Django ContentTypes 简单使用

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可检查特殊的组合外键。在对应模型加上引用字段即可和普通外键一样访问和查询。

猜你喜欢

转载自blog.csdn.net/Fe_cow/article/details/93334844