一.概念:
Django有一个用来跟踪所以已安装App的models的框架,名为contenttypes。本文标题ContentType其实是Django原生App之一contenttypes的实现基础。
该应用提供了一种高级的、通用的接口用来管理和维护我们应用程序的models。
拿之前的drf_server项目来举例,该框架会为每一个APP的models创建对应的信息,该信息存储在content_type表中,请看下图:
如上图所示,每一个App对应多个model, 存储在名为django_content_type的表中,该表只有id, app_label和model三个字段。第一个字段存储的是model的序号,下文中,contenttypes框架正是通过model的id找到该model,app_label存储的是App的名称,而model字段则存储的是每一个model的名称。
1.安装使用:
使用django-admin startproject命令创建一个Django项目后,在INSTALLED_APPS列表中会出现该框架,如下图所示:
2.什么时候需要用到该框架
如果查看官方文档,你只会发现一些官方的对于该框架的解释,但是在什么地方能够发挥它的最大价值,官方文档中并没有详细说明
(1).一个简单的需求
假设现在我需要开发一个课程App,该app共有如下课程表:
共四个课程表:操作系统基础,Python基础,面向对象,Web框架。
(2).需求
学生学完每个课程的最后一门课后,会获得该门课程的优惠券和该课程的总优惠券,比如学生学完操作系统原理课程后,会获得操作系统原理通关优惠券和操作系统基础课程优惠券两张优惠券。
(3).第一次优化
看到如此多的优惠券表,作为程序员,必须不能忍,要好好优化优化,不然,这就是给自己挖了一个巨大的坑,以后扩展和维护肯定会特别不方便可以将如此多的优惠券表合成一张表。
这样我们就实现了使用一张优惠券表来存储所有的优惠券和对应的课程的关系信息(使用了课程id),注意每个课程id字典都是该优惠券表的一个外键字段。
(4).第二次优化
经过第一次优化,表数量减少了,但是对这样的表进行增删改查操作将会非常麻烦,设计优惠券和课程之间的关系,无非就是为了增删改查这些操作,只要能够通过课程id找到它所对应的所有优惠券,或者通过优惠券能唯一定位到某一个具体的课程,这就是我的本质需求
现在的问题是,课程id是重复的,每一个表里面的id都是从1开始计数,而优惠券里面的id如果仅仅只是存储课程id,很显然,就无法帮助我们唯一定位到具体的课程。那么,通过什么方式来唯一定位id呢?
请看下图的优化:
我们使用的table_id和table_row_id来唯一定义某个课程表中的某一个课程,首先,我们必须通过某一个信息定位到具体的表,然后再通过课程id来定位具体的课程, 那么,table_id从哪里来呢?
还记得我们上面的django_content_type表吗?它里面有三个字段,其中id字段存储的就是整个Django项目中所有表的序号id。
所有我们的优惠券表共有四个字段,id字段,coupon_name字段存储优惠券信息,table_id字段存储的是课程表的id,它应该是一个外键,关联到django_content_type表,table_row_id字段,该字段的性质也是外键,但是不能具体指向某一个表,因为,该字段存储的是所有表的id。
优化到这里,差不多了,算是比较合适的表结构设计了。那么,我们应该怎么做呢?看起来还有点麻烦,特别是table_row_id字段。不是特别好实现。
二. 使用content-types建表
1.导入:
from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
2.建表
class OperationSystem(models.Model): """ id course_name 1 "计算机基础" 2 "计算机组成原理" 3 "操作系统原理" """ course_name = models.CharField(max_length=32) coupons = GenericRelation(to="Coupon") def __str__(self): return self.course_name class PythonBasic(models.Model): """ id course_name 1 "数据类型" 2 "字符编码" 3 "文件操作" """ course_name = models.CharField(max_length=32) coupons = GenericRelation(to="Coupon") def __str__(self): return self.course_name class Oop(models.Model): """ id course_name 1 "面向对象三大特性" 2 "元类" """ course_name = models.CharField(max_length=32) coupons = GenericRelation(to="Coupon") def __str__(self): return self.course_name class WebFramework(models.Model): """ id course_name 1 "web框架原理" 2 "ORM" """ course_name = models.CharField(max_length=32) coupons = GenericRelation(to="Coupon") def __str__(self): return self.course_name class Coupon(models.Model): """ id name content_type_id object_id 1 "操作系统优惠券" 9 2 2 "Python 优惠券" 9 2 """ coupon_name = models.CharField(max_length=32) content_type = models.ForeignKey(ContentType, verbose_name="关联到django的ContentType表", on_delete=models.CASCADE) object_id = models.PositiveIntegerField(verbose_name="关联表中的数据行ID") content_object = GenericForeignKey("content_type", "object_id") def __str__(self): return self.coupon_name
3.小结:
- 导入模块一个是ContentType这个model
- 另一个是GenericForeignKey和GenericRelation
优惠券表包含coupon_name, 该字段是一个外键,关联到ContentType表;
还包含object_id字段, 该字段存储课程id;
还包含object_id字段, 该字段存储课程id;
另外最重要的是content_object字段,它是GenericForeignKey这个类的实例化对象,我们创建这个对象时,需要把上面两个字段作为参数传递给它。
以后,我们对于优惠券表的所有数据操作,只要是涉及到需要查找优惠券对应的课程,或者通过课程查找其对应的优惠券,都是通过content_object来进行的。
值得注意的是,该字段并不真实存在表中,Coupon表结构如下图所示:
4.基本数据操作
先插入一些基础数据,也就是第一张图中展示的数据。
5.操作ContentType
ContentType是一个表,这个表提供一些方法以便我们进行数据操作:
>>> from django.contrib.contenttypes.models import ContentType >>> course_type = ContentType.objects.get(app_label="course", model="oop") >>> course_type Out[7]: <ContentType: oop> # 获取类名:字符串形式 >>> course_type.model Out[8]: 'oop' # 获取model,之后可以通过objects.all来查询数据 >>> course_type.model_class() Out[9]: course.models.Oop # 查询数据 >>> course_type.get_object_for_this_type(course_name="元类") Out[10]: <Oop: 元类>
6.添加数据
给课程字符编码添加一个字符编码通关优惠券:
>>> from course.models import PythonBasic >>> pb_obj = PythonBasic.objects.get(id=2) >>> from course.models import Coupon >>> Coupon.objects.create(coupon_name="字符编码通关优惠券", content_object=pb_obj) Out[15]: <Coupon: 字符编码通关优惠券>
查看数据是否添加成功:
可以看到,是第九张表的第二行数据,查看django_content_type表发现,该课程所在的表Python基础表,在整个项目中的确是第九张表,而且该课程在该表中是第二行数据,id为2。
这样,我们就可以通过表的id和数据id具体定位到某一个课程,然后给该课程绑定一个优惠券。当然还可以给它绑定多个优惠券。
>>> from course.models import Coupon >>> from course.models import PythonBasic >>> pb_obj = PythonBasic.objects.get(id=2) >>> Coupon.objects.create(coupon_name="字符编码通关优惠券", content_object=pb_obj)
可以看到,我们定位具体的课程信息是通过content_object这个并不存在Coupon表中的字段来操作的。
7.删除数据
删除字符编码通关优惠券对应的所有课程
>>> Coupon.objects.filter(coupon_name="字符编码通关优惠券").delete()
or
>>> pb_obj = PythonBasic.objects.get(id=2)
>>> ob_obj.coupons.all().delete()
8.修改数据:
...
9.查询数据
查询面向对象通关优惠券绑定了那些课程
>>> coupon_obj = Coupon.objects.filter(coupon_name="面向对象通关优惠券").first() >>> coupon_obj.content_type # <PythonBasic: 字符编码>
使用反向查询字段查看字符编码课程共有哪些优惠券
>>> pb_obj = PythonBasic.objects.get(id=2) >>> pb_obj.coupons.all() Out[35]: <QuerySet [<Coupon: 字符编码通关优惠券>, <Coupon: 面向对象通关优惠券>]>
三总结:
综上,我们会发现,在我们的项目里面,如果一个表跟其他多个表都有外键关系,而且外键都在该表上,那么我们可以使用contenttypes框架来帮助我们管理这些外键关系,本质上,contenttypes的实现基于ContentType这张表,而且,除字段名不一样外,通过该框架设计的表与我们自己优化出来的表结构是一致的。
- Django contenttypes框架介绍
- 通过contenttypes对数据库表结构进行优化
- 通过contenttypes进行增删改查操作