Django-ContentType-signals 实现牛逼玩法
一、ContentType
在django中,有一个记录了项目中所有model元数据的表,就是ContentType,表中一条记录对应着一个存在的model,所以可以通过一个ContentType表的id和一个具体表中的id找到任何记录,及先通过ContenType表的id可以得到某个model,再通过model的id得到具体的对象。
- 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
這个类主要作用是记录每个app中的model。例如,我们在自己的app中创建了如下几个model:post,event。迁移之后,我们来查看一下ContentType這个数据表中生成的数据:
如上图,生成了app与model的对应关系。那么,這个主要有什么用呢?别急,听我慢慢道来。
我们在View视图中,来这样玩玩:
- def demo(request):
- obj = models.ContentType.objects.get(id=10)
- print(obj.model_class()) # <class 'app01.models.Post'>
- return HttpResponse('............')
看到,我通过model_class就可以获取对应的类。也就是说,今后,我们如果自己定义model如果有外键关联到這个ContentType上,我们就能找到对应的model名称。
二、Django-ContentType-signals
django的signal结合contenttypes可以实现好友最新动态,新鲜事,消息通知等功能。总体来说这个功能就是在用户发生某个动作的时候将其记录下来或者附加某些操作,比如通知好友。要实现这种功能可以在动作发生的代码里实现也可以通过数据库触发器等实现,但在django中,一个很简单的方法的就是使用signals。
当django保存一个object的时候会发出一系列的signals,可以通过对这些signals注册listener,从而在相应的signals发出时执行一定的代码。
使用signals来监听用户的动作有很多好处,1、不管这个动作是发生在什么页面,甚至在很多页面都可以发生这个动作,都只需要写一次代码来监听保存object这个动作就可以了。2、可以完全不修改原来的代码就可以添加监听signals的功能。3、你几乎可以在signals监听代码里写任何代码,包括做一些判断是不是第一次发生此动作还是一个修改行为等等。
想要记录下每个操作,同时还能追踪到这个操作的具体动作。
*首先用信号机制,监听信号,实现对信号的响应函数,在响应函数中记录发生的动作(记录在一张记录表,相当于下文的Event)。
*其次就是为了能追踪到操作的具体动作,必须从这张表中得到相应操作的model,这就得用到上面说的ContentType。
对于新鲜事这个功能来说就是使用GenericRelation来产生一个特殊的外键,它不像models.ForeignKey那样,必须指定一个Model来作为它指向的对象。GenericRelation可以指向任何Model对象,有点像C语言中 void* 指针。
这样关于保存用户所产生的这个动作,比如用户写了一片日志,我们就可以使用Generic relations来指向某个Model实例比如Post,而那个Post实例才真正保存着关于用户动作的完整信息,即Post实例本身就是保存动作信息最好的地方。这样我们就可以通过存取Post实例里面的字段来描述用户的那个动作了,需要什么信息就往那里面去取。而且使用Generic relations的另外一个好处就是在删除了Post实例后,相应的新鲜事实例也会自动删除。
怎么从这张操作记录表中得到相应操作的model呢,这就得用到fields.GenericForeignKey,它是一个特殊的外键,可以指向任何Model的实例,在这里就可以通过这个字段来指向类似Post这样保存着用户动作信息的Model实例。
先来看看model:
- from django.db import models
- from django.contrib.auth.models import User
- from django.contrib.contenttypes import fields
- from django.db.models import signals
- class Post(models.Model):
- author = models.ForeignKey(User)
- title = models.CharField(max_length=255)
- content = models.TextField()
- created = models.DateTimeField(u'发表时间', auto_now_add=True)
- updated = models.DateTimeField(u'最后修改时间', auto_now=True)
- events = fields.GenericRelation('Event')
- def __str__(self):
- return self.title
- def description(self):
- return u'%s 发表了日志《%s》' % (self.author, self.title)
- class Event(models.Model):
- user = models.ForeignKey(User)
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object= fields.GenericForeignKey('content_type', 'object_id')
- created = models.DateTimeField(u'事件发生时间', auto_now_add=True)
- def __str__(self):
- return "%s的事件: %s" % (self.user, self.description())
- def description(self):
- return self.content_object.description()
- def post_post_save(sender, instance, signal, *args, **kwargs):
- """
- :param sender:监测的类:Post类
- :param instance: 监测的类:Post类
- :param signal: 信号类
- :param args:
- :param kwargs:
- :return:
- """
- post = instance
- event = Event(user=post.author, content_object=post)
- event.save()
- signals.post_save.connect(post_post_save, sender=Post)
#signals.post_save.connect(post_post_sace,sender=Book)可以监听多个类
只要model中有object的保存操作,都将执行post_post_save函数,故可以在这个接受函数中实现通知好友等功能。
前面说到django在保存一个object的时候会发出一系列signals,在这里我们所监听的是signals.post_save这个signal,这个signal是在django保存完一个对象后发出的,django中已定义好得一些signal, 在django/db/models/signal.py中可以查看,同时也可以自定义信号。
利用connect这个函数来注册监听器, connect原型为:
def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
第一个参数是要执行的函数,第二个参数是指定发送信号的Class,这里指定为Post这个Model,对其他Model所发出的signal并不会执行注册的函数。
instance这个参数,即刚刚保存完的Model对象实例。创建事件的时候看到可以将post这个instance直接赋给generic.GenericForeignKey类型的字段,从而event实例就可以通过它来获取事件的真正信息了。
最后有一点需要的注意的是,Post的Model定义里现在多了一个字段:
content_object= GenericRelation(‘Event’)
通过这个字段可以得到与某篇post相关联的所有事件,最重要的一点是如果没有这个字段,那么当删除一篇post的时候,与该post关联的事件是不会自动删除的。反之有这个字段就会进行自动的级联删除
三、ContentType其他案例总结
案例一、调查问卷表设计
例如:设计如下类型的调查问卷表:问卷类型包括(打分,建议,选项),先来看看一个简单的问答,
- 您最喜欢吃什么水果?
- A.苹果 B.香蕉 C.梨子 D.橘子
对于上面一个类型的问答,我们可以知道,一个问卷系统主要包括:问卷,问卷中每个题目,每个题目的答案,以及生成问卷记录。常规设计表如下:
- from django.db import models
- from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
- from django.contrib.contenttypes.models import ContentType
- class Survery(models.Model):
- """
- 问卷
- ID name by_class creator
- 1 第一次班级调查 三年级五班 李老师
- """
- name = models.CharField(verbose_name="调查问卷名称", max_length=128, unique=True)
- by_class = models.ForeignKey(verbose_name="问卷调查班级", to="ClassList")
- date = models.DateTimeField(verbose_name="问卷创建日期", auto_now_add=True)
- creator = models.ForeignKey(verbose_name="创建者", to="UserInfo")
- class SurveryItem(models.Model):
- """
- 问卷题目
- ID survery name date answer_type
- 1 1(代表上面创建的第一次班级调查) 您最喜欢吃什么水果? xxx-xxx-xx 1
- 1 1(代表上面创建的第一次班级调查) 您最喜欢什么玩具? xxx-xxx-xx 2
- 1 1(代表上面创建的第一次班级调查) 您最喜欢什么英雄人物? xxx-xxx-xx 3
- """
- survery = models.ForeignKey(verbose_name='问卷', to='Survery')
- name = models.CharField(verbose_name="调查问题", max_length=255)
- date = models.DateField(auto_now_add=True)
- answer_type_choices = (
- (1, "打分(1~10分)"),
- (2, "单选"),
- (3, "建议"),
- )
- answer_type = models.IntegerField(verbose_name="问题类型", choices=answer_type_choices, default=1)
- class SurveryChoices(models.Model):
- """
- 问卷选项答案(针对选项类型)
- ID item content points
- 1 2 A 10分
- 1 2 B 9分
- 1 2 C 8分
- 1 2 D 7分
- """
- item = models.ForeignKey(verbose_name='问题', to='SurveryItem')
- content = models.CharField(verbose_name='内容', max_length=256)
- points = models.IntegerField(verbose_name='分值')
- class SurveryRecord(models.Model):
- """
- 问卷记录
- ID survery student_name survery_item score single suggestion date
- 1 1 1 1 10分 null null xxxxx
- 1 1 1 2 null A null xxxxx
- 1 1 1 3 null null XXXXX xxxxx
- """
- survery = models.ForeignKey(Survery, verbose_name="问卷")
- student_name = models.ForeignKey(verbose_name="学员姓名", to="Student")
- survery_item = models.ForeignKey(verbose_name="调查项", to='SurveryItem')
- score = models.IntegerField(verbose_name="评分", blank=True, null=True)
- single = models.ForeignKey(verbose_name='单选', to='SurveryChoices', blank=True, null=True)
- suggestion = models.TextField(verbose_name="建议", max_length=1024, blank=True, null=True)
- date = models.DateTimeField(verbose_name="答题日期", auto_now_add=True)
但是,如果我有另外一个需求,也需要与SurveryRecord建立外键关系,那么此时应该怎么做呢?是再给上面的表增加一个外键,然后重新修改数据库么?显然是不能,一旦数据库被创建了,我们几乎很少再去修改数据,如果再给其添加额外字段,无疑会带来不必要的麻烦。为此,我们可以利用Django自带的ContentType类,来做这件事情。
下面来看看经过修改以后的model:
- from django.db import models
- from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
- from django.contrib.contenttypes.models import ContentType
- class Survery(models.Model):
- """
- 问卷
- ID name by_class creator
- 1 第一次班级调查 三年级五班 李老师
- """
- name = models.CharField(verbose_name="调查问卷名称", max_length=128, unique=True)
- by_class = models.ForeignKey(verbose_name="问卷调查班级", to="ClassList")
- date = models.DateTimeField(verbose_name="问卷创建日期", auto_now_add=True)
- creator = models.ForeignKey(verbose_name="创建者", to="UserInfo")
- class SurveryItem(models.Model):
- """
- 问卷题目
- ID survery name date answer_type
- 1 1(代表上面创建的第一次班级调查) 您最喜欢吃什么水果? xxx-xxx-xx 1
- 1 1(代表上面创建的第一次班级调查) 您最喜欢什么玩具? xxx-xxx-xx 2
- 1 1(代表上面创建的第一次班级调查) 您最喜欢什么英雄人物? xxx-xxx-xx 3
- """
- survery = models.ForeignKey(verbose_name='问卷', to='Survery')
- name = models.CharField(verbose_name="调查问题", max_length=255)
- date = models.DateField(auto_now_add=True)
- answer_type_choices = (
- (1, "打分(1~10分)"),
- (2, "单选"),
- (3, "建议"),
- )
- answer_type = models.IntegerField(verbose_name="问题类型", choices=answer_type_choices, default=1)
- class SurveryChoices(models.Model):
- """
- 问卷选项答案(针对选项类型)
- ID item content points
- 1 2 A 10分
- 1 2 B 9分
- 1 2 C 8分
- 1 2 D 7分
- """
- item = models.ForeignKey(verbose_name='问题', to='SurveryItem')
- content = models.CharField(verbose_name='内容', max_length=256)
- points = models.IntegerField(verbose_name='分值')
- surveryrecord = GenericRelation("SurveryRecord")
- class Score(models.Model):
- item = models.ForeignKey(verbose_name='问题', to='SurveryItem')
- points = models.IntegerField(verbose_name='分值')
- surveryrecord = GenericRelation("SurveryRecord")
- class Suggestion(models.Model):
- item = models.ForeignKey(verbose_name='问题', to='SurveryItem')
- suggests = content = models.CharField(verbose_name='内容', max_length=256)
- surveryrecord = GenericRelation("SurveryRecord")
- class SurveryRecord(models.Model):
- """
- 问卷记录
- ID survery student_name survery_item content_type object_id
- 1 1 1 1 1 1
- 1 1 1 2 1 2
- 1 1 1 3 1 3
- """
- survery = models.ForeignKey(Survery, verbose_name="问卷")
- student_name = models.ForeignKey(verbose_name="学员姓名", to="Student")
- survery_item = models.ForeignKey(verbose_name="调查项", to='SurveryItem')
- content_type = models.ForeignKey(ContentType, blank=True, null=True)
- object_id = models.PositiveIntegerField(blank=True, null=True)
- content_object = GenericForeignKey('content_type', 'object_id') # 這个字段不会再数据库中存在,只是在查询时有用
- date = models.DateTimeField(verbose_name="答题日期", auto_now_add=True)
案例二、优惠券系统设计
应用场景:某一在线教育网,需要为每位积极客户发一些观看视频的优惠券,但是,对于不同类型的视频,优惠券是不同。比如:有一个普通课程,需要发一些满200减30的优惠券,而又有精品课程,需要发满100键70的优惠券。根据以上需求,我们很快就知道,需要三张表,学位课程表,课程表以及优惠券表,那么,这三张表又是如何关联的呢?
我们先来看看下面這个:
- #A 学位课程表结构
- # ID 名称
- # 1 学位课1
- # 2 学位课2
- #B普通课程表
- #ID 名称
- #1 普通课1
- #2 普通课2
- #优惠券表
- #ID 优惠券名称 A(FK) B(FK)
- #1 通用优惠券 null null # 两个都为空,说明全场都可以使用
- #2 满100-10 1 null # 给学位课程创建优惠券
- #3 满200-30 null 1 # 给普通课程创建优惠券
还是与上面一样,如果再来一种课程,上面的优惠券表还需要额外新增一列,为了解决這个问题,我们可以使用ContentType类来实现上述需求。
- from django.db import models
- from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
- from django.contrib.contenttypes.models import ContentType
- # Create your models here.
- class DegreeCourse(models.Model):
- """学位课程
- ID 名称
- 1 学位课1
- 2 学位课2
- """
- name = models.CharField(max_length=128, unique=True)
- x1 = GenericRelation("Coupon")
- class Course(models.Model):
- """课程
- ID 名称
- 1 普通课1
- 2 普通课2
- """
- name = models.CharField(max_length=128, unique=True)
- class Coupon(models.Model):
- """优惠券生成规则
- ID 优惠券名称 A FK B.FK c.FK
- 1 通用 null null
- 2 满100-10 8 1
- 3 满200-30 8 2
- 4 满200-30 9 1
- ID 优惠券名称 content_type_id(表) object_id(表中数据ID)
- 1 通用 null null
- 2 满100-10 8 1
- 3 满200-30 8 2
- 4 满200-30 9 1
- 总结:
- """
- name = models.CharField(max_length=64, verbose_name="活动名称")
- brief = models.TextField(blank=True, null=True, verbose_name="优惠券介绍")
- # 那个表?
- content_type = models.ForeignKey(ContentType, blank=True, null=True)
- # 对象ID
- object_id = models.PositiveIntegerField("绑定课程", blank=True, null=True, help_text="可以把优惠券跟课程绑定")
- content_object = GenericForeignKey('content_type', 'object_id')
- #
- #
- #
- #
- # class Homework(models.Model):
- # """
- # ID User Name score
- # 1 吴昊 第一模块 30
- # 2 吴昊 第二模块 80
- # 3 吴昊 第三模块 100
- #
- # """
- # name = models.CharField(max_length=32)
- #
- # score_choices = (
- # (100,'A'),
- # (80,'B'),
- # (60,'C'),
- # (30,'D'),
- # )
- # score = models.IntegerField(choices=score_choices)
- #
- # user = models.ForeignKey('User')
- #
- #
- # class Record(models.Model):
- # """
- # ID User Name score
- # 1 吴昊 第一模块 10 5
- # 2 吴昊 第二模块 8 10
- # """
- # name = models.CharField(max_length=32)
- # score_choices = (
- # (100, 'A'),
- # (80, 'B')
- # )
- # score = models.IntegerField(choices=score_choices)
- #
- # class ScoreRecord(models.Model):
- # """
- # ID Name 表 对象
- # 1 作业太差 1
- # 2 作业太好 1
- # 5 看的太快 null 1
- # """
- # name = models.CharField(max_length=32)
- # content_type = models.ForeignKey(ContentType, blank=True, null=True)
- # # 对象ID
- # object_id = models.PositiveIntegerField("绑定课程", blank=True, null=True, help_text="可以把优惠券跟课程绑定")
- #
- #
- from django.shortcuts import render,HttpResponse
- from django.contrib.contenttypes.models import ContentType
- from . import models
- def test(request):
- # models.UserInfo.objects.filter()
- # content = ContentType.objects.get(app_label='app01',model='userinfo')
- # model_class = content.model_class()
- # print(model_class.objects.all())
- # 给学位课1或普通课创建优惠券
- d1 = models.DegreeCourse.objects.get(id=1)
- models.Coupon.objects.create(name='优惠券', brief='2000-30', content_object=d1)
- # d1 = models.Course.objects.get(id=1)
- # models.Coupon.objects.create(name='优惠券', brief='100-90', content_object=d1)
- # 当前优惠券,绑定的课程?
- obj = models.Coupon.objects.get(id=1)
- # print(obj.content_object)
- # 当前课程,都有哪些优惠券?
- # obj = models.DegreeCourse.objects.get(id=1)
- # print(obj.x1.all())
- # v = models.DegreeCourse.objects.values('name','x1__brief')
- # print(v)
- return HttpResponse('...')
根据ContentType字段查询关联字段操作
总之,如果一个表与其他表有多个外键关系,我们可以通过ContentType来解决这种关联。
f
Django-ContentType-signals 实现牛逼玩法的更多相关文章
- 玩转type类型(牛逼克拉斯 )
一.前言 一说起type()方法肯定有很多人都知道这不就是查看某个对象类型的嘛,其实不然,他还有更牛逼的应用------创建类 >>> type(1) <class 'int' ...
- 为什么我会认为SAP是世界上最好用最牛逼的ERP系统,没有之一?
为什么我认为SAP是世界上最好用最牛逼的ERP系统,没有之一?玩过QAD.Tiptop.用友等产品,深深觉得SAP是贵的有道理! 一套好的ERP系统,不仅能够最大程度承接适配企业的管理和业务流程,在技 ...
- 15天玩转redis —— 第八篇 你不得不会的事务玩法
我们都知道redis追求的是简单,快速,高效,在这种情况下也就拒绝了支持window平台,学sqlserver的时候,我们知道事务还算是个比较复杂的东西, 所以这吊毛要是照搬到redis中去,理所当然 ...
- ExceptionLess新玩法 -- 审计日志
审计日志 这算是一个挺酷的功能,把每个请求都记录下来,之前在abp中看到过这个功能,配合可视化的界面,简直是在装逼 看到了exceptionless后,心念一动,我也可以根据它做一个审计日志的功能.这 ...
- 科学家有了钱以后,真是挺吓人的——D.E.Shaw的牛逼人生
科学家有了钱以后,真是挺吓人的——D.E.Shaw的牛逼人生 黑科技,还是要提D.E.Shaw Research这个奇异的存在. 要讲这个黑科技,我们可能要扯远一点,先讲讲D.E. Shaw这个人是怎 ...
- 【Python基础】random 的高级玩法
random 模块的高级玩法 1.python 随机产生姓名 方式一: import random xing = [ '赵', '钱', '孙', '李', '周', '吴', '郑', '王', ' ...
- 历数依赖注入的N种玩法
历数依赖注入的N种玩法 在对ASP.NET Core管道中关于依赖注入的两个核心对象(ServiceCollection和ServiceProvider)有了足够的认识之后,我们将关注的目光转移到编程 ...
- 2 URL的玩法
preface 这里我主要说说flask的URL玩法 include: 动态URL规则 自定义URL转换器 HTTP方法 唯一的URL 构造URL 跳转和重定向 动态URL规则 URL规则可以添加变量 ...
- Chrome 控制台新玩法-向输出到console的文字加样式
Chrome 控制台新玩法-向输出到console的文字加样式 有兴趣的同学可以文章最后的代码复制贴到控制台玩玩. Go for Code 在正常模式下,一般只能向console 控制台输出简单的文字 ...
随机推荐
- Python的生成器进阶玩法
Python的生成器进阶玩法 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.yield的表达式形式 #!/usr/bin/env python #_*_coding:utf-8 ...
- Python基础【day03】:文件操作(六)
一.概述 我们工作中需要经常操作文件,下面就讲讲如何用Python操作文件 1.文件操作的流程: 打开文件,得到文件句柄赋值给一个变量 通过文件句柄,对文件进行操作 关闭文件 二.入门 1.语法 op ...
- Spark记录-实例和运行在Yarn
#运行实例 #./bin/run-example SparkPi 10 #./bin/spark-shell --master local[2] #./bin/pyspark --master l ...
- 运行fdisk命令时,弹出 bash:fdisk:command not found
原因:命令fdisk 不在你的命令搜索路径中 第一种解决办法,将fdisk添加到你的命令搜索路径中 首先查看你当前的命令搜索路径: [root@host sbin]# echo $PATH/usr/k ...
- Mogodb 学习一
0.MongoDB和关系型数据的几个重要对象对比 MongoDB中的数据库.集合.文档 类似于关系型数据库中的数据库.表.行 MongoDB中的集合是没有模式的,所以可以存储各种各样的文档 1.启动M ...
- 20155303 2016-2017-2 《Java程序设计》第六周学习总结
20155303 2016-2017-2 <Java程序设计>第六周学习总结 课堂笔记 高效学习法推荐 看视频学习(2h)→ 以代码为中心看课本,思考运行结果并验证(3h)→ 课后作业验证 ...
- vue-cli 3.0 开启 Gzip 方法
vue.config.js const path = require('path') const CompressionWebpackPlugin = require('compression-web ...
- 使用 scm-manager 搭建 git/svn 代码管理仓库(一)
1.在官网上下载scm-manager 下载地址 https://www.scm-manager.org/download/ 选择下载文件 2. 配置java 环境 参照文章:https://jin ...
- mybatis输入输出映射——(五)
0.#{}与${}区别 #{}实现的是向prepareStatement中的预处理语句中设置参数值,sql语句中#{}表示一个占位符即?. <!-- 根据id查询用户信息 --> < ...
- if 语句 写了return 报错