Django ContentTypes 简单使用:
一、简单介绍:
contenttypes 是Django内置的一个应用,可以追踪项目中所有app和model的对应关系,并记录在ContentType表中
。主要用来创建模型间的通用关系(generic relation)。 可以跟踪Django项目中安装的所有模型(Model),提供用于处理模型的高级通用接口
。
二、分析ContentTypes应用:
当使用django-admin初始化一个django项目的时候,可以看到在默认的INSTALL_APPS
已经包含了django.contrib.contenttypes:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes', # 这里已经引入
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
查看一下django.contrib.contenttypes.models文件:
@python_2_unicode_compatible
class ContentType(models.Model):
app_label = models.CharField(max_length=100)
model = models.CharField(_('python model class name'), max_length=100)
objects = ContentTypeManager()
class Meta:
verbose_name = _('content type')
verbose_name_plural = _('content types')
db_table = 'django_content_type'
unique_together = (('app_label', 'model'),)
def __str__(self):
return self.name
@property
def name(self):
model = self.model_class()
if not model:
return self.model
return force_text(model._meta.verbose_name)
def model_class(self):
"Returns the Python model class for this type of content."
try:
return apps.get_model(self.app_label, self.model)
except LookupError:
return None
def get_object_for_this_type(self, **kwargs):
return self.model_class()._base_manager.using(self._state.db).get(**kwargs)
def get_all_objects_for_this_type(self, **kwargs):
return self.model_class()._base_manager.using(self._state.db).filter(**kwargs)
def natural_key(self):
return (self.app_label, self.model)
可以看到ContentType就是一个简单的django model
,而且它在数据库中的表的名字为django_content_type。
对Django的model进行migrate之后,就可以发现在数据库中出现了一张默认生成的名为django_content_type的表
。记录当前的Django项目中所有model所属的app(即app_label属性)以及model的名字(即model属性)。
如果没有建立任何的model,默认django_content_type是这样的:
select * from django_content_type;
1|admin|logentry
2|auth|group
3|auth|user
4|auth|permission
5|contenttypes|contenttype
6|sessions|session
contenttypes是对model的一次封装,因此可以通过contenttypes动态的访问model类型,而不需要每次import具体的model类型。
- ContentType实例提供的接口:
- ContentType.model_class():获取当前ContentType类型所代表的模型类。
- ContentType.get_object_for_this_type():使用当前ContentType类型所代表的模型类做一次get查询。
- ContentType管理器(manager)提供的接口:
- ContentType.objects.get_for_id():通过id寻找ContentType类型,这个跟传统的get方法的区别就是它跟get_for_model共享一个缓存。
- ContentType.objects.get_for_model():通过model或者model的实例来寻找ContentType类型。
三、应用场景:
比如,我们要做一个举报系统,举报的主体有,问题、回答、评论,那么在数据库设计时,可以将每个举报主体跟举报的原因和举报人进行关联:
from django.db import models
class Question(models.Model):
"""问题表"""
title = models.CharField(verbose_name=u'标题', max_length=128)
class Answer(models.Model):
"""回答表"""
brief = models.CharField(verbose_name=u'回答摘要', max_length=225, null=True)
class Comment(models.Model):
"""评论表"""
content = models.CharField(verbose_name=u'评论内容', max_length=255, null=True)
class ReportRecord(models.Model):
"""举报表"""
reporter = models.IntegerField(verbose_name=u'举报者')
question = models.ForeignKey(to=Question, null=True)
answer = models.ForeignKey(to=Answer, null=True)
comment = models.ForeignKey(to=Comment, null=True)
如果这样设计的话,存储数据冗余的问题,因为每记录一个主体的信息的时候,其余的列都为null
四、使用Contenttypes应用设计表结构:
通过使用contenttypes 应用中提供的特殊字段GenericForeignKey
,我们可以很好解决上述问题。
- 在model中定义
ForeignKey字段
,并关联到ContentType表。通常这个字段命名为'content_type'
- 在model中定义
PositiveIntegerField字段
,用来存储关联表中的主键
。通常这个字段命名为'object_id'
- 在model中定义
GenericForeignKey字段
,传入上述两个字段的名字(不会生成表字段)。通常这个字段命名为'content_object'
- 进行反向查询时,可以在问题、回答、评论表中定义
GenericRelation字段定义反向关系
(不会生成表字段)。
4.1:创建表结构:
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
class Question(models.Model):
"""问题表"""
title = models.CharField(verbose_name=u'标题', max_length=128)
# 用于反向查询到ReportRecord表, 不会生成表字段
reports = GenericRelation(to='ReportRecord')
class Answer(models.Model):
"""回答表"""
brief = models.CharField(verbose_name=u'回答摘要', max_length=225, null=True)
# 用于反向查询到ReportRecord表, 不会生成表字段
reports = GenericRelation(to='ReportRecord')
class Comment(models.Model):
"""评论表"""
content = models.CharField(verbose_name=u'评论内容', max_length=255, null=True)
# 用于反向查询到ReportRecord表, 不会生成表字段
reports = GenericRelation(to='ReportRecord')
class ReportRecord(models.Model):
"""举报表"""
reporter = models.IntegerField(verbose_name=u'举报者')
# 此表会关联到ContentType表 相当于跟每个表都是FK
content_type = models.ForeignKey(to=ContentType, null=True)
# 用来存储关联表中的主键
object_id = models.PositiveIntegerField()
# 被举报对象
content_object = GenericForeignKey('content_type', 'object_id')
-
进行数据库表迁移:
python manage.py makemigrations app_name[应用的名称] python manage.py migrate app_name[应用的名称]
4.2:ContentTypes读写:
1.url.py,路由配置:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^v1/$', views.TestView.as_view())
]
2.views.py, 视图内容:
-
创建单表记录,
使用create()方法
:class TestView(APIView): def post(self, request, *args, **kwargs): """创建记录""" # 获取user_id ,举报者的ID user_id = request.data.get('userId') try: # 获取Question对象 question_obj = models.Question.objects.get(pk=1) except models.Question.DoesNotExist: return HttpResponse('资源不存在') # 使用create()方法创建 models.ReportRecord.objects.create(reporter=user_id, content_object=question_obj) return HttpResponse('创建成功')
-
创建单表记录,
使用save()方法
:class TestView(APIView): def post(self, request, *args, **kwargs): """创建记录""" user_id = request.data.get('userId') try: question_obj = models.Question.objects.get(pk=3) except models.Question.DoesNotExist: return HttpResponse('资源不存在') # 使用save()方法进行创建 cur_report = models.ReportRecord(reporter=user_id, content_object=question_obj) cur_report.save() return HttpResponse('创建成功')
-
使用ContentType创建
:from django.contrib.contenttypes.models import ContentType class TestView(APIView): def post(self, request, *args, **kwargs): """创建记录""" user_id = request.data.get('userId') try: question_obj = models.Question.objects.get(pk=3) except models.Question.DoesNotExist: return HttpResponse('资源不存在') # 获取Question的ContentType对象 ques_content_type = ContentType.objects.get_for_model(question_obj) cur_report = models.ReportRecord(content_type=ques_content_type, object_id=question_obj.id, reporter=user_id) # 保存对象 cur_report.save() return HttpResponse('创建成功')
查看表结构的内容:
id reporter object_id content_type_id (举报表主键) (举报者的user_id) (question表中的主键ID) (question表在django_content_type表中的ID) 1 1 1 13 该方式稍微麻烦点,通常用于读取数据。
因为读取数据
无法直接使用content_object作为条件
:-
1.因为content_object字段是GenericForeignKey。
-
2.该字段没有真正被创建,不是普通意义的字段。无法被解析成SQL语句。
-
3.举个读取的栗子:
from django.contrib.contenttypes.models import ContentType class TestView(APIView): def get(self, request, *args, **kwargs): """获取表记录""" user_id = request.data.get('userId') try: question_obj = models.Question.objects.get(pk=3) except models.Question.DoesNotExist: return HttpResponse('资源不存在') # 获取Question的ContentType对象 ques_content_type = ContentType.objects.get_for_model(question_obj) # 使用content_type 读取数据 report_list = models.ReportRecord.objects.filter( content_type=ques_content_type, object_id=question_obj.id) print(report_list) return HttpResponse('请求成功!')
# 打印结果: <QuerySet [<ReportRecord: ReportRecord object>, <ReportRecord: ReportRecord object>]>
-
-
ContentType表对象有model_class() 方法,取到对应model
:from django.contrib.contenttypes.models import ContentType class TestView(APIView): def get(self, request, *args, **kwargs): """获取表记录""" # ContentType表对象有model_class() 方法,取到对应model content_obj = ContentType.objects.filter( app_label='apps', model='question').first() # 表名小写 questions = content_obj.model_class() # 相当于models.Question question_list = questions.objects.all() print(question_list) # 打印结果:<QuerySet [<Question: Question object>, # <Question: Question object>]> # uestion: Question object>, <Question: Question object>]> return HttpResponse('请求成功!')
-
通过Question表
反向查询
:class TestView(APIView): def get(self, request, *args, **kwargs): """获取表记录""" # 查询问题ID=1的, 都有哪些举报记录 try: question_obj = models.Question.objects.get(pk=3) except models.Question.DoesNotExist: return HttpResponse('资源不存在') # 前提需要在,question表添加字段:reports = GenericRelation(to='ReportRecord') report = question_obj.reports.all() print(report) return HttpResponse('请求成功!')
-
如果
不设置反向查询
:class TestView(APIView): def get(self, request, *args, **kwargs): """获取表记录""" try: question_obj = models.Question.objects.get(pk=3) except models.Question.DoesNotExist: return HttpResponse('资源不存在') # ContentType表对象有model_class() 方法,取到对应model content_obj = ContentType.objects.filter( app_label='apps', model='question').first() # 表名小写 # 进行查询举报表 reports = models.ReportRecord.objects.filter( content_type=content_obj, object_id=question_obj.pk) print(reports) # 打印结果:<QuerySet [<ReportRecord: ReportRecord object>, # <ReportRecord: ReportRecord object>]> return HttpResponse('请求成功!')
4.3:总结:
ContentTypes可检查特殊的组合外键。在对应模型加上引用字段即可和普通外键一样访问和查询。