使用 django channels 作为邮件发送队列
2016-11-22 14:18
549 查看
使用 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 models from django.contrib.auth.models import User class 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 forms from django.utils.crypto import get_random_string from .models import Invitation class 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 route from .consumers import send_invite channel_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 logging from django.contrib.sites.models import Site from django.core.mail import EmailMessage from django.utils import timezone from invitations.models import Invitation logger = 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 forms from django.utils.crypto import get_random_string from channels import Channel from .models import Invitation class 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 os import channels.asgi os.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 -v2 worker: 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发送邮件
- python 使用Django 的 邮件模块 发送邮件
- Django中使用多线程发送邮件
- Linux系统下使用mail发送一封简单的Internet邮件【以及验证邮件是否发送成功sendmail -bp,必须是root用户才可以使用此命令查看邮件消息队列中的内容】
- Django-Python Django 使用 QQ / 新浪邮箱发送邮件配置
- Django中使用多线程发送邮件
- django使用smtp发送邮件
- ===注册时使用消息队列 发送邮件。遇到的【环境问题】总结。==比代码问题难找!!!
- 使用django + celery + redis 异步发送邮件
- django使用QQ企业邮箱发送邮件
- SAE中使用Django发送邮件遇到的几个问题
- Django中使用多线程发送邮件
- 在django1.2+python2.7环境中使用send_mail发送邮件 推荐
- 使用SMTP协议发送邮件
- ASP.Net环境下使用Jmail组件发送邮件
- C#使用CDO发送邮件
- C#使用CDO发送邮件
- 使用 MAPI 实现邮件发送
- C#使用CDO发送邮件
- 使用ASP+Jmail4.3发送邮件,随便发点感概......:)