Django——ContentType(与多个表建立外键关系)及ContentType-signals的使用

时间:2023-02-23 23:17:47

一、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 @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):
"""
Returns an object of this type for the keyword arguments given.
Basically, this is a proxy around this object_type's get_object() model
method. The ObjectNotExist exception, if thrown, will not be caught,
so code that calls this method should catch it.
"""
return self.model_class()._base_manager.using(self._state.db).get(**kwargs) def get_all_objects_for_this_type(self, **kwargs):
"""
Returns all objects of this type for the keyword arguments given.
"""
return self.model_class()._base_manager.using(self._state.db).filter(**kwargs) def natural_key(self):
return (self.app_label, self.model)

ContentType源码

  這个类主要作用是记录每个app中的model。例如,我们在自己的app中创建了如下几个model:post,event。迁移之后,我们来查看一下ContentType這个数据表中生成的数据:

      Django——ContentType(与多个表建立外键关系)及ContentType-signals的使用

  如上图,生成了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 优惠券名称 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="优惠券介绍") # 给你的model设置一个 ForeignKey 字段到ContentType. 一般命名为“content_type”.用来放想要关联的那个表在contenttype表中的id
content_type = models.ForeignKey(ContentType, blank=True, null=True)
# 对象ID,用来储存你想要关联的model主键值,对于大多数model,,这是一个 PositiveIntegerField字段。并且通常命名为 “object_id”.
object_id = models.PositiveIntegerField("绑定课程", blank=True, null=True, help_text="可以把优惠券跟课程绑定") content_object = GenericForeignKey('content_type', 'object_id')

app01/models.py

根据ContentType字段查询关联字段操作

from django.shortcuts import render,HttpResponse
from django.contrib.contenttypes.models import ContentType
def test(request): content = ContentType.objects.get(app_label='app01',model='coupon')
model_class = content.model_class()
print(content) # coupon
print(model_class) # <class 'app01.models.Coupon'>
print(model_class.objects.all()) #<QuerySet [<Coupon: 通用>, <Coupon: 满100-10>, <Coupon: 满200-30>, <Coupon: 满200-30>]> # 给学位课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)
#或 models.Coupon.objects.create(name='优惠券', brief='100-90', content_object_id=1) # 当前优惠券,绑定的课程?
# obj = models.Coupon.objects.get(id=2)
# print(obj.content_object) #普通课1
'''通过content_object直接找到与其关联的models对象'''
#正向查找:models对象.content_object得到的是models对象 # 当前课程,都有哪些优惠券?
# obj = models.DegreeCourse.objects.get(id=1)
# print(obj.x1.all()) # [<Coupon: 满200-30>]>
# v = models.DegreeCourse.objects.values('name','x1__brief')
# print(v) # <QuerySet [{'name': '学位课1', 'x1__brief': ''}, {'name': '学位课2', 'x1__brief': None}]> # 反向查找:models对象.反向关联字段.all() 得到的是QuerySet对象 return HttpResponse('...')

app01/views.py

  总之,如果一个表与其他表有多个外键关系,我们可以通过ContentType来解决这种关联。

Django——ContentType(与多个表建立外键关系)及ContentType-signals的使用的更多相关文章

  1. 给已有数据的oracle表建立外键关系

    PS:这里是给自己做个备忘,下次遇到同类问题的时候,方便查找: 客户在有主外键关系的2张表进行页面删除时报错已有子记录,运维后台处理的时候应该找出相应的数据,先删除子记录,在删主表记录:但客户要的急, ...

  2. MYSQL数据表建立外键

    MySQL创建关联表可以理解为是两个表之间有个外键关系,但这两个表必须满足三个条件1.两个表必须是InnoDB数据引擎2.使用在外键关系的域必须为索引型(Index)3.使用在外键关系的域必须与数据类 ...

  3. laravel模型表建立外键约束的使用:

    模型: //表->posts class Post extends Model { //关联用户: public function user(){ //belongsTo,第一个参数:外键表,第 ...

  4. django中两张表有外键关系的相互查找方法,自定义json编码方法

    两张通过外键联系的表,如何在一张表上根据另一张表上的属性查找满足条件的对象集? 平常查找表中数据的条件是python中已有的数据类型,通过名字可以直接查找.如果条件是表中外键列所对应表的某一列,该如何 ...

  5. &period;NET MVC 表主外键关系 JSON 无限循环 方案二(推荐)

    public class JsonResultObject:JsonResult { private Newtonsoft.Json.JsonSerializerSettings Settings { ...

  6. MySQL表存在外键关系无法清空数据的解决方案

    先 SET FOREIGN_KEY_CHECKS=0; 然后delete删除,再 SET FOREIGN_KEY_CHECKS=1;

  7. 使用ContentType处理大量的外键关系

    问题分析 在之前的一个商城的项目中使用了mysql, 提到mysql就是外键, 多对多等等一系列的表关系 因为是一个商城的项目, 这里面有优惠券, 商品有很多的分类, 不同的商品又有不同的优惠券 其实 ...

  8. SQL Server中建立外键的方法

    在SQL中建立外键约束,可以级联查询表中的数据,在C#代码生成器中,也能根据外键关系生成相应的外键表数据模型.外键也可防止删除有外键关系的记录,一定程度上保护了数据的安全性. 步骤: 1.要建立外键关 ...

  9. &lbrack;MySQL&rsqb;表创建外键失败&colon;ERROR 1005 &lpar;HY000&rpar;&colon; Can&&num;39&semi;t create table &lpar;errno&colon; 150&rpar;

    在数据库中建立一个新表(表引擎为InnoDB)时, 需要用到外键, 所以就在建表的时候加了一句foreign key (column) references table_name.但是执行时出现 ER ...

随机推荐

  1. 深入理解css3中nth-child和 nth-of-type的区别

    在css3中有两个新的选择器可以选择父元素下对应的子元素,一个是:nth-child 另一个是:nth-of-type. 但是它们到底有什么区别呢? 其实区别很简单::nth-of-type为什么要叫 ...

  2. xfs磁盘(文件)碎片查看和整理

    网上有些帖子说XFS不用做碎片整理,其实是错误的.XFS用延迟写入等技术确实可以减少碎片的出现,但是如果服务器用了几年,并且文件操作比较频繁,还是会出现碎片的,应该整理.注意:在Debian中XFS相 ...

  3. JavaScript中的原型和对象机制

    1.对象相关的一些语言特性 JavaScript里所有的东西都是对象, 对象是属性的集合.要知道,函数也是对象, 能够作为变量的值. 返回值. 参数或者属性的值. 函数对象特殊的地方是能通过&quot ...

  4. tomcat session cluster

    Session的生命周期 以前在学习的时候没怎么注意,今天又回过头来仔细研究研究了一下Session的生命周期. Session存储在服务器端,一般为了防止在服务器的内存中(为了高速存取),Sessi ...

  5. 动画&lowbar; &lowbar; Android应用开发之所有动画使用详解

    转载: http://blog.csdn.net/yanbober/article/details/46481171 题外话:有段时间没有更新博客了,这篇文章也是之前写了一半一直放在草稿箱,今天抽空把 ...

  6. Android代码混淆官方实现方法

    首先查看一下 “project.properties” 这个文件: # This file is automatically generated by Android Tools.# Do not m ...

  7. POJ3321 Apple Tree(DFS序)

    题目,是对一颗树,单点修改.子树查询.典型的dfs序入门题. DFS序可以将一颗树与子树们表示为一个连续的区间,然后用线段树来维护:感觉算是树链剖分的一种吧,和轻重链剖分不同的是这是对子树进行剖分的. ...

  8. postgresql之数据字典

    greenplum是基于postgresql开发的分布式数据库,里面大部分的数据字典是一样的.我们在维护gp的时候对gp的数据字典比较熟悉,特此分享给大家.在这里不会详细介绍每个字典的内容,只会介绍常 ...

  9. 清除css、javascript及背景图在浏览器中的缓存

    在实际项目开发过过程中,页面是上传到服务器上的.而为了减少服务器的压力,让用户少加载,浏览器会将图片.css.js缓存到本地中,以便下次访问网站时使用.这样做不仅减少了服务器的压力,并且也减少了用户的 ...

  10. linux&lowbar;shell 编程学习-初识she&&num;39&semi;ll

    一.she'll编程规范 1.she'll脚本命名一般为英文的大小写; 2.不能用特殊符号.空格来命名; 3.she'll脚本后缀以.sh结尾; 4.不建议she'll命名为纯数字,一般以脚本功能命名 ...