使用 django channels 作为邮件发送队列
来源:互联网 发布:软件开发前期准备工作 编辑:程序博客网 时间:2024/04/30 11:53
使用 Django Channels 作为邮件发送队列
本文是一篇翻译文,是我在学习Django Channels的看的一篇文章, 英语好的同学建议直接看原文.
原文地址为 : Using Django Channels as an Email Sending Queue
Channels 是由 Andrew Godwin 领导的一个项目, 旨在能给 Django 带来” 本地异步处理” 的能力. 大多数关于 Channels 的教程都是都把关注点放在了Channels 给Django带来的 WebSockets的处理能力上. 但是 Channels 还有一个重要的功能,就是异步任务. 基于这一点, Channels 就可以替代 Celery 或 RQ 在大部分项目中的任务, 而且使用起来更为的自然.
为了证明这一点, 让我们使用Channels来为一个Django project添加一个非阻塞的邮件发送功能.
首先, 我们需要一个 Invitation model.
from django.db import modelsfrom django.contrib.auth.models import Userclass Invitation(models.Model): email = models.EmailField() sent = models.DateTimeField(null=True) sender = models.ForeignKey(User) key = models.CharField(max_length=32, unique=True) def __str__(self): return "{} invited {}".format(self.sender, self.email)
对应的 ModelForm.
from django import formsfrom django.utils.crypto import get_random_stringfrom .models import Invitationclass InvitationForm(forms.ModelForm): class Meta: model = Invitation fiels = ['email'] def save(self, *args, **kwargs): self.instance.key = get_random_string(32).lower() return super(InvitationForm, self).save(*args, **kwargs)
关于如何在View中使用这个 form就留给读者了, 我们现在要做的是, 当 Invitation在前端被创建时,被立即送到后台进行处理. 现在我们需要安装Channels.
pip install channels
我们打算使用 Redis 作为 message的容器, 这个容器被称为 “层(layer)”在Channels中,它位于我们的 web 处理和Channels worker 之间.所以我们需要安装相应的Redis库(注意这里按照的asgi-redis的作用是提供Channels使用Redis的方法)
pip install asgi-redis
我们打算把 Redis 作为首选的 Channels 层. (Channels团队同样提供了另外两种选择方案, in-memory layer 和 database layer. 其中 database layer不建议使用) 如果我们的开发环境还没有安装 Redis, 我们需要在我们的OS上安装Reids. 下面提供 Debian/Linux-based system的安装方法:
apt-get install redis-server
如果是 Mac用户, 我们会使用 Homebrew来安装:
brew install redis
经过上面的教程, 我们假定我们的开发环境具备如下条件:
安装了 Redis
安装了 Channels 和 asgi-redis
现在,可以开始把Channels添加到我们的项目中来了.在项目的 settings.py文件中. 添加 ‘channels’到 INSTALL_APPS中, 并且添加 channels配置模块
INSTALL_APPS = ( ..., 'channels',)CHANNEL_LAYERS = { "default":{ "BACKEND":"asgi_redis.RedisChannelLayer", "CONFIG":{ "hosts":[os.environ.get('REDIS_URL', 'redis://localhost:6379')], }, "ROUTING":"myproject.routing.channel_routing", },}
让我们来看一下 CHANNEL_LAYERS 块. 它是否看起来和Django的数据库settings很像呢? 这并不奇怪. 就像我们在settings中有一个默认的数据库配置一样, 这里我们定义了一个默认的Channels配置. 我们的配置使用 Redis作为后端, 并指定了Redis服务的url. 最后指定了一个 routing配置, routing配置和 urls.py文件的工作方式类似.(在这里我们假定项目名称为’myproject’, 你应该替换成你实际的项目名)
由于我们仅仅是使用Channels在后台进行email发送服务, 因此我们的routing.py 文件显的很简单.
from channels.routing import routefrom .consumers import send_invitechannel_routing = [ route('send-invite', send_invite),]
正如所期望的,routing.py内容的结构和我们的urls.py. 上面定义的内容的含义如下:
我们定义了一个 名为’send-invite’的rout. 上面我们定义Channels的默认配置时有一个 “ROUTING”:”myproject.routing.channel_routing” .注意这里的路径就是我们的上面的 channel_routing. 所以 上面Channel收到的东西会被送到 ‘send_invite’ 消费者进行消费. 在我们的app中consumers.py文件和Django的标准app中的views.py文件是类似的, consumers.py才是我们正真处理email 发送的地方.
import loggingfrom django.contrib.sites.models import Sitefrom django.core.mail import EmailMessagefrom django.utils import timezonefrom invitations.models import Invitationlogger = logging.getLogger('email')def send_invite(message): try: invite = Invitation.objects.get( id=message.content.get('id'),) except Invitation.DoseNotExist: logger.error("Invitation to send not found") return subject = "You've been invited!" body = "Go to https://%s/invites/accept/%s/ to join!" % ( Site.objects.get_current().domain, invite.key, ) try: message = EmailMessage( subject=subject, body=body, from_email="Invites <invites@%s.com>" % Site.objects.get_current().domain, to=[invite.email,], ) message.send() invite.sent = timezone.now() invite.save() except: logger.execption('Problem sending invite %s' % (invite.id))
Consumers 从一个给定的channel中消耗 messages, 其中messages是一列的数据对象. message中的数据必须是可以json化的,只有这样它才可以被存在Channel layer(本例是:Redis)中,并进行传递操作.在我们的例子中, 我们使用的唯一数据是要发送的邀请的ID. 我们从数据库获取invite对象, 基于该对象构建电子邮件,然后尝试发送电子邮件. 如果成功,我们在邀请对象上设置一个”已发送”时间戳. 如果失败, 我们记录一个错误.
到这里为止,我们还有一个问题没有解决. 就是如何在合适的时间把 message送到 ‘send-invite’ channel呢? 我们使用下面的方法
from django import formsfrom django.utils.crypto import get_random_stringfrom channels import Channelfrom .models import Invitationclass InvitationForm(forms.ModelForm): class Meta: model = Invitation fields = ['email'] def save(self, *args, **kwargs): self.instance.key = get_random_string(32).lower() response = super(InvitationForm, self).save(*args, **kwargs) notification = { 'id':self.instance.id, } Channel('send-invite').send(notification) return response
我们从channels 包导入Channel, 在我们的invite要保存时发送一个”数据” 到 ‘send-invite’ channel
现在,我们准备要测试了! 假设我们将表单连接到View, 并在我们的setting.py中设置正确的电子邮件主机设置, 我们可以测试在我们的应用后台使用Channel发送电子邮件邀请. 关于Channels在开发中的惊人的事情是,我们正常启动我们的 devserver, 并且, 至少在我的经验看来, 它可以工作了.
python manage.py runserver
祝贺!我们已经添加后台任务到Django application 中了. 让我们来使用Channel吧
现在, 在系统能够在实际环境中运行前,我都不相信它可以工作, 所以让我们谈一下如何部署的问题. Channels 文档 里面有很好的写到这部分的内容, 但我使用的是 Heroku, 所以我会像 JacobKaplan-Moss的优秀tutorial一样完成本教程.
我们在 wsgi.py文件同级的目录下创建一个 asgi.py文件.
import osimport channels.asgios.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")channel_layer = channels.asgi.get_channel_layer()
(再次提醒各位, 把上面的 myproject换成自己真实的项目名)
然后, 我们更新我们的 Profile 来包含 Channels process, running under Daphne, and a worker process.
web: daphne myproject.asgi:channel_layer --port $PORT --bind 0.0.0.0 -v2worker: python manage.py runworker --settings=myproject.settings -v2
我们可以使用 Heroku的免费 Redis主机, 部署我们的应用程序, 并享受在后台发送电子邮件,而不会阻塞我们的应用服务请求.
希望本教程能激励你探索Channels的后台任务功能, 并考虑当Channel成为Django的核心时准备好你的应用程序. 我想我们正在朝着一个未来前进, 到时Django可以做到更好的 开箱即用(out-of-the-box), 我很高兴看到我们所建立的!
特别感谢 Jacob Kaplan-Moss, Chris Clark 和 Erich Blume 提供的反馈
- 使用 django channels 作为邮件发送队列
- 使用Django发送邮件
- Spring使用gmail作为邮件发送服务器
- Django中使用多线程发送邮件
- django使用QQ企业邮箱发送邮件
- python 使用Django 的 邮件模块 发送邮件
- Django发送邮件
- Django发送html邮件
- django 中发送邮件
- django 发送邮件
- django发送邮件
- Django 发送邮件
- Django 发送邮件配置
- django邮件发送
- django发送邮件
- Django 应用 -- 发送邮件
- 发送邮件队列
- Django中发送邮件send_mail
- 玩转Android Camera开发(三):国内首发---使用GLSurfaceView预览Camera 基础拍照demo
- ora-01653表空间大小不足,以验证成功
- 定时 监控 shell 服务宕机自动重启,并发送短信通知
- vs2008 + qt4.8(both in win7 32bit and 64bit)
- WPF-单实例运行设置
- 使用 django channels 作为邮件发送队列
- 将Unity的脚本封装为dll文件(使用monodevelop编译器)
- Shadow Map原理和改进
- 关于win 10系统无法保存运行命令的解决方法
- 使用nprobe抓取网络数据包存到本地
- 玩转Android Camera开发(四):预览界面四周暗中间亮,只拍摄矩形区域图片(附完整源码)
- 关于retrofit的Post请求
- Windows系统 MongoDB 各个64位版本下载地址
- swift的惊喜与疑问