`
xiaolin0199
  • 浏览: 573362 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

使用Django的 signals 和 contenttypes 实现新鲜事功能

阅读更多

 

看到很多SNS网站上,像校内,5G都有一个很棒的功能,就是登录之后在自己的首页,可以看到自己好友最新发生的动态。于是想到使用django其实可以非常简单的实现这个功能,并且效果比现在SNS网站所用的更好。

总体来说这个功能就是在用户发生某个动作的时候将其记录下来,我不知道别人是怎么实现的,也许是直接在发生那个动作的代码块里写死,也许是使用数据库的触发器之类,但是在django中,一个很简单的方法的就是使用signal。

什么是signals:

最初看到signals是在django的The database API文档,当保存一个object的时候,会发生下面的这些事情(原文):
What happens when you save?

When you save an object, Django performs the following steps:

Emit a ``pre_save`` signal. This provides a notification that an object is about to be saved. You can register a listener that will be invoked whenever this signal is emitted. (These signals are not yet documented.)

Pre-process the data. Each field on the object is asked to perform any automated data modification that the field may need to perform.

Most fields do no pre-processing — the field data is kept as-is. Pre-processing is only used on fields that have special behavior. For example, if your model has a DateField with auto_now=True, the pre-save phase will alter the data in the object to ensure that the date field contains the current date stamp. (Our documentation doesn’t yet include a list of all the fields with this “special behavior.”)

Prepare the data for the database. Each field is asked to provide its current value in a data type that can be written to the database.

Most fields require no data preparation. Simple data types, such as integers and strings, are ‘ready to write’ as a Python object. However, more complex data types often require some modification.

For example, DateFields use a Python datetime object to store data. Databases don’t store datetime objects, so the field value must be converted into an ISO-compliant date string for insertion into the database.

Insert the data into the database. The pre-processed, prepared data is then composed into an SQL statement for insertion into the database.

Emit a ``post_save`` signal. As with the pre_save signal, this is used to provide notification that an object has been successfully saved. (These signals are not yet documented.)

简单来说就是当django保存一个object的时候会发出一系列的signals,可以通过对这些signals注册listener,从而在相应的signals发出时执行一定的代码。关于signals的文档django还没有整理出来,不过找了些资料来看了看,不算很难懂,基本使用还是很简单的。

使用signals来监听用户的动作有很多好处,1、不管这个动作是发生在什么页面,甚至在很多页面都可以发生这个动作,都只需要写一次代码来监听保存object这个动作就可以了。2、可以完全不修改原来的代码就可以添加监听signals的功能。3、你几乎可以在signals监听代码里写任何代码,包括做一些判断是不是第一次发生此动作还是一个修改行为等等。

鉴于本人表达能力有限,如果看到这里感觉稀里糊涂,可以考虑先看一下本文底部所列出的一些资料获取更多信息。

现在需要面对的就是第二个问题,监听到用户动作之后如何完整有效地保存用户这一动作,保存一个字符串来描述这个动作?通过外键来指向某个表中的某条记录?

保存一个字符串来描述这个动作是一个很高效的方法,但是缺乏灵活性,比如用户发表了一篇日志,但是很快又删了,如何把这个多余的新鲜事记录找出来同时删除?想修改已经保存了的字符串描述该怎么办?

而通过外键来指向某个表中的某条记录虽然可以解决删除问题,但是需要为不同类型的动作各自添加一张表来对应,如果以后有新的功能实现需要添加新表,如何可以简单来进行扩展呢?

而这些django的 contenttypes framework就可以很好的解决。

什么是contenttypes framework(原文):

Django includes a “contenttypes” application that can track all of the models installed in your Django-powered project, providing a high-level, generic interface for working with your models.

这句话听上去很难理解,不过对于新鲜事这个功能来说就是使用Generic relations来产生一个特殊的外键,它不像models.ForeignKey那样,必须指定一个Model来作为它指向的对象。Generic relations可以指向任何Model对象,有点像C语言中 void* 指针。


这样关于保存用户所产生的这个动作,比如用户写了一片日志,我们就可以使用Generic relations来指向某个Model实例比如Post,而那个Post实例才真正保存着关于用户动作的完整信息,即Post实例本身就是保存动作信息最好的地方。这样我们就可以通过存取Post实例里面的字段来描述用户的那个动作了,需要什么信息就往那里面去取。而且使用Generic relations的另外一个好处就是在删除了Post实例后,相应的新鲜事实例也会自动删除。

说了这么多,还是直接来看代码更实际些,看看到底怎么来实现吧。

假如有一个叫Post的Model,以及一个用来记录用户事件的Model定义如下:

 

# -*- coding: utf-8 -*-  
from django.db import models  
from django.contrib.auth.models import User  
from django.contrib.contenttypes.models import ContentType  
from django.contrib.contenttypes import generic  
  
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)  
  
  
    def __unicode__(self):  
        return self.title  
  
  
class Event(models.Model):  
    user = models.ForeignKey(User)  
    content_type = models.ForeignKey(ContentType)  
    object_id = models.PositiveIntegerField()  
      
    event = generic.GenericForeignKey('content_type', 'object_id')  
      
    created = models.DateTimeField(u'事件发生时间', auto_now_add = True)  
      
    def __unicode__(self):  
        return  self.user.username + u'的事件' 

 

Post这个Model很普通没什么好说的,Event这个Model有3个很特殊的字段,content_type是一个普通的外键指向一个叫ContentType的Model,而ContentType这个Model就很特殊了,文档描述是

Instances of ContentType represent and store information about the models installed in your project, and new instances of ContentType are automatically created whenever new models are installed.


就是说一个ContentType 实例 存储了 某个Model 的一些信息,通过这些信息就可以还原出那个Model。其实ContentType 的存储的信息也非常简单,其定义如下:

class ContentType(models.Model):
    name = models.CharField(max_length=100)
    app_label = models.CharField(max_length=100)
    model = models.CharField(_('python model class name'),   max_length=100)
    objects = ContentTypeManager()

 

而通过app_label和model这2个字段,使用django.db.models.get_model这个方法就可以找出原来所对应的Model。而有了原来的Model的定义,再通过使用主键,就可以找到这个Model所对应的某条记录了,object_id这个字段正是用来存储这个主键的,

所以content_type和object_id这两个字段加起来就可以表达在一个project中所存在的所有Model的某个实例了。当然我们最终并不需要直接和这两个字段打交道,而是通过另外一个看起来很奇快的字段,generic.GenericForeignKey是一个特殊的外键,可以指向任何Model的实例,在这里就可以通过这个字段来指向类似Post这样保存着用户动作信息的Model实例。

定义完了Model之后就是来写一下signals部分的代码了,将原来的文件修改成如下这样:

 

#coding=utf-8
from django.db import models
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.db.models import signals
from django.dispatch import dispatcher

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 = generic.GenericRelation('Event')

    def __unicode__(self):
        return self.title

    def descrption(self):
        return u'%s 发表了日志《%s》' % (self.author, self.title)

    class Admin:
        pass


class Event(models.Model):
    user = models.ForeignKey(User)
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()

    event = generic.GenericForeignKey('content_type', 'object_id')

    created = models.DateTimeField(u'事件发生时间', auto_now_add = True)

    def __unicode__(self):
        return u"%s的事件: %s" % (self.user, self.descrption())

    def descrption(self):
        return self.event.descrption()

    class Admin:
        pass



#0.96
def post_post_save(sender, instance, signal, *args, **kwargs):
    post = instance
    if post.created == post.updated:
        event = Event(user=post.author,event = post)
        event.save()

dispatcher.connect(post_post_save, signal=signals.post_save, sender=Post)

#1.0
from django.db.models.signals import post_save
def post_post_save(sender, instance, created, *args, **kwargs):   
    if created:
        event = Event(user=instance.user,event = instance)   
        event.save()

post_save.connect(post_post_save,sender=Post)
 

前面说到django在保存一个object的时候会发出一系列signals,在这里我们所监听的是signals.post_save这个signal,这个signal是在django保存完一个对象后发出的,利用dispatcher.connect这个函数来注册监听器,第一个参数是要执行的函数,第二个参数是要监听的signal,第三个参数是指定发送信号的Class,这里指定为Post这个Model,对其他Model所发出的signal并不会执行注册的函数。而我们所定义的需要执行的函数可以将signal所发出的一些参数定义为需要执行的函数的参数,从而在函数里面进行使用,获取更多的信息,这里使用了instance这个参数,即刚刚保存完的Model对象实例。在函数里通过比较日志发布时间和修改时间是否相等来判断是第一次发表还是修改后的保存,并且只为第一次发表这个动作创建一个相应的事件。创建事件的时候看到可以将post这个instance直接赋给generic.GenericForeignKey类型的字段,从而event实例就可以通过它来获取事件的真正信息了,在这里,如果有其他类型的Model实例,当然也可以赋值给generic.GenericForeignKey类型的字段了。

现在事件已经保存起来了,如何来得到事件的一个简短描述呢?可以给自己做一个假定,event这个字段所指向的Model都有一个叫做descrption的方法,那么我们只管调用这个方法就可以得到描述了。至于Post里面的那个descrption方法,可以根据需要,自己存取post实例的字段来获取信息,包括直接在描述字符串里面包含日志的url等。



最后有一点需要的注意的是,Post的Model定义里现在多了一个字段:


events = generic.GenericRelation('Event')



通过这个字段可以得到与某篇post相关联的所有事件,最重要的一点是如果没有这个字段,那么当删除一篇post的时候,与该post关联的事件是不会自动删除的。反之有这个字段就会进行自动的级联删除。



现在有了以上这些,如果你将这些Model定义和signals定义,添加到自己某个app,然后到admin页面添加一篇post试试,添加完后应该会自动多出了一条对应的event记录,修改一个post不会再添加event记录,而删除一篇post会自动删除相应的event记录。(现在django的newforms-admin分支已经合并了,可能关于本文的代码admin部分的定义已经无效,如何修改,可以参考此文:http://jinhao.iteye.com/blog/218112)



这就是一个新鲜事功能简单的实现原理,而关于 signals 和 contenttypes 的更多资料可以参考以下这些:

官方的 contenttypes 文档:http://www.djangoproject.com/documentation/contenttypes/

contenttypes 例子:http://www.djangoproject.com/documentation/models/generic_relations/

Generic Relation在SharePlat的使用:http://blog.donews.com/limodou/archive/2006/12/31/1106217.aspx

django 的 contribs 之 contenttype:http://codeplayer.blogspot.com/2006/09/django-contribs-contenttype.html

django signals的wiki页:http://code.djangoproject.com/wiki/Signals

另一篇关于signals的文章:http://www.mercurytide.co.uk/whitepapers/django-signals/

08年9月6日 更新:

现在Django 官方的文档已经进行了重构,关于signal的文档也出来了,参见:

http://docs.djangoproject.com/en/dev/topics/signals/

http://docs.djangoproject.com/en/dev/ref/signals/

另外关于在回复讨论中提到的时间误差问题,看了 post_save 信号的描述之后,发现其实它会自己传一个参数 created , 以说明是新创建的还是更新的方式调用了 save() ,用这个参数显然比用时间来判断更好.

见 http://docs.djangoproject.com/en/dev/ref/signals/#post-save
分享到:
评论

相关推荐

    django内置组件ContentTypes.7z

    总之,Django的`ContentTypes` 组件是其强大功能的一个体现,它允许我们构建高度灵活和可扩展的应用程序,同时保持代码的简洁和模块化。理解和掌握`ContentTypes` 的使用对于提升Django开发能力至关重要。

    使用Django+MySQL实现的在线电影推荐系统源码.zip

    使用Django+MySQL实现的在线电影推荐系统源码 使用Django+MySQL实现的在线电影推荐系统源码 使用Django+MySQL实现的在线电影推荐系统源码 使用Django+MySQL实现的在线电影推荐系统源码 使用Django+MySQL实现...

    Django实现登录-注册等功能

    在本文中,我们将深入探讨如何使用Django框架来实现用户登录和注册功能,这对于任何Web应用程序来说都是核心组件。Django是一个用Python编写的高级Web框架,它鼓励快速开发和简洁实用的设计理念。 首先,我们需要...

    使用Django实现的文件分享系统源码.zip

    使用Django实现的文件分享系统,实现搜索功能,分享功能,用户分享文件查询,写该项目的目的主要是用于保存一些电子书籍和学习资料,方便自己和其他人查找资料,所以对文件大小进行限制最大为5M。 使用Django...

    Python-通过Django在web上实现xshell的功能

    【Python-通过Django在web上实现xshell的功能】 Django是Python中广泛使用的Web开发框架,它提供了高效、简洁且实用的工具,用于构建高质量的Web应用。本项目的目标是利用Django来构建一个类似xshell的Web应用,...

    Django实现商城网站源码.zip

    Django实现商城网站源码 Django实现商城网站源码 Django实现商城网站源码 Django实现商城网站源码 Django实现商城网站源码 Django实现商城网站源码 Django实现商城网站源码 Django实现商城网站源码 Django...

    Python Django实现简单购物车功能

    Django是一个强大且高效的Web开发框架,它提供了丰富的功能和工具,使得构建电子商务网站变得更加容易。 首先,我们需要创建一个新的Django项目和应用。在命令行中,使用以下命令初始化项目和名为`shopcar`的应用:...

    django layui表单加图片上传功能(Django,layui).zip

    在本文中,我们将深入探讨如何在Django框架中结合Layui实现图片上传功能。Layui是一款优秀的前端UI框架,而Django是Python后端开发的流行框架,两者结合可以构建出美观且功能强大的Web应用。以下是实现这一功能的...

    Python基于Django的物资管理系统 实现二维码生成与扫码功能

    python基于django 实现的物资管理系统,有二维码生成和扫码功能。 此作品为作者学习Python后的第一个作品,所有的视图处理都放在了VIEW视图里,而且鄙人比较懒,并不想在model加函数了,程序结构很臃肿,对于一些...

    Django实现的图书借阅系统源码.zip

    Django实现的图书借阅系统 Django实现的图书借阅系统 Django实现的图书借阅系统 Django实现的图书借阅系统 Django实现的图书借阅系统 Django实现的图书借阅系统 Django实现的图书借阅系统 Django实现的图书...

    Scrapy爬取数据,并使用Django框架+PyEcharts实现可视化大屏

    Scrapy爬取去哪儿网,并使用Django框架+PyEcharts实现可视化大屏。 Scrapy爬取去哪儿网,并使用Django框架+PyEcharts实现可视化大屏。 Scrapy爬取去哪儿网,并使用Django框架+PyEcharts实现可视化大屏。 Scrapy爬取...

    使用Django、python简单的实现学生选课管理系统.zip

    在本项目中,“使用Django、python简单的实现学生选课管理系统”是一个初学者在学习Python编程和Django框架时创建的实践项目。这个系统旨在模拟一个基础的学生选课流程,帮助我们理解如何将Python和Django结合应用于...

    基于Python+Django简单实现文件上传下载功能源码.zip

    基于Python+Django简单实现文件上传下载功能源码 基于Python+Django简单实现文件上传下载功能源码 基于Python+Django简单实现文件上传下载功能源码 基于Python+Django简单实现文件上传下载功能源码 基于...

    Django实现支付宝付款和微信支付的示例代码

    本文将向您展示如何使用Django框架来实现支付宝支付和微信支付功能,并提供示例代码以供参考。 首先,要实现支付宝支付,开发者需要在蚂蚁金服开放平台上注册账号,创建应用并配置相关的API密钥。这一过程称为...

    使用Django开发的天天生鲜商城源码.zip

    使用Django开发的天天生鲜商城源码 使用Django开发的天天生鲜商城源码 使用Django开发的天天生鲜商城源码 使用Django开发的天天生鲜商城源码 使用Django开发的天天生鲜商城源码 使用Django开发的天天生鲜...

    使用Django搭建的音乐网站管理系统源码.zip

    使用Django搭建的音乐网站管理系统,具有完整的音乐搜索,在线播放,下载,评论,登陆,榜单,分类等功能。 使用Django搭建的音乐网站管理系统,具有完整的音乐搜索,在线播放,下载,评论,登陆,榜单,分类等...

    Django1.6_利用Form实现注册登录注销修改密码

    登录功能通常使用Django内置的`AuthenticationForm`,并结合`login`视图来实现: ```python from django.contrib.auth import authenticate, login from django.contrib.auth.forms import AuthenticationForm from...

    使用Django框架开发的企业OA管理系统源码.zip

    使用Django框架开发的企业OA管理系统源码 使用Django框架开发的企业OA管理系统源码 使用Django框架开发的企业OA管理系统源码 使用Django框架开发的企业OA管理系统源码 使用Django框架开发的企业OA管理系统源码 ...

    Django引用ztree实现数据库表导入树状目录

    在IT行业中,Web开发是一个广泛且重要的领域,而Python的Django框架因其高效、易用和功能强大而受到开发者们的青睐。本篇文章将深入探讨如何在Django项目中结合ZTree插件,从MySQL数据库中读取数据表,并将这些数据...

    源代码基于Django的博客系统python实现.zip

    基于Django的博客系统python实现.zip基于Django的博客系统python实现.zip基于Django的博客系统python实现.zip基于Django的博客系统python实现.zip基于Django的博客系统python实现.zip基于Django的博客系统python实现...

Global site tag (gtag.js) - Google Analytics