好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Django发送邮件

 

当我们创建一个网站时,经常会有发送邮件的需要。无论是用户注册,还是用户忘记密码,又或者是用户下单后进行付款确认,都需要发

送不同的邮件。所以发送邮件的需求实际上是非常重要的,而且如果如果不在一开始就建造结构清晰的邮件服务,到后面可能会是一团糟。

基本的邮件发送

假设我们希望在用户注册到我们的网站。我们可以参照Django文档,向验证通过并创建成功的用户发送邮件。具体实现如下:

import?logging

from?rest_framework.views?import?APIView
from?django.http?import?JsonResponse
from?django.core.mail?import?send_mail

from?users.models?import?User

logger?=?logging.getLogger('django')


class?RegisterView(APIView):

????def?post(self,?request):
????????#?Run?validations
????????if?not?request.data:
????????????return?JsonResponse({'errors':?'User?data?must?be?provided'},?status=400)
????????if?User.objects.filter(email=request.data['email']).exists():
????????????return?JsonResponse({'errors':?'Email?already?in?use'},?status=400)
????????try:
????????????#?Create?new?user
????????????user?=?User.objects.create_user(email=request.data['email'].lower())
????????????user.set_password(request.data['password'])
????????????user.save()
????????????
????????????#?Send?welcome?email
????????????send_mail(
????????????????subject='Welcome!',
????????????????message='Hey?there!?Welcome?to?our?platform.',
????????????????html_message='<p><strong>Het?there!</strong>?Welcome?to?our?platform.</p>'
????????????????from_email='from@example测试数据',
????????????????recipient_list=[user.email],
????????????????fail_silently=False,
????????????)
????????????
????????????return?JsonResponse({'status':?'ok'})
????????except?Exception?as?e:
????????????logger.error('Error?at?%s',?'register?view',?exc_info=e)
????????????return?JsonResponse({'errors':?'Wrong?data?provided'},?status=400)

当然肯定文档中所说的那样,你也必须提前设定好一些重要的配置项,例如EMAIL_HOST和EMAIL_PORT。

很好!现在我们已经发送了欢迎邮件!

创建一个mailer类

正如我之前所说,在我们的应用中不同的模块可能都需要发送邮件,所以最好有一个电子邮件服务或者mailer类来处理所有的邮件请求。更简单,因为我们不再需要每次都去翻遍全部代码。

iimport?logging

from?django.conf?import?settings
from?django.core.mail?import?send_mail

from?users.models?import?User

logger?=?logging.getLogger('django')


class?BaseMailer():
????def?__init__(self,?to_email,?subject,?message,?html_message):
????????self.to_email?=?to_email
????????self.subject?=?subject
????????self.message?=?message
????????self.html_message?=?html_message

????def?send_email(self):
????????send_mail(
????????????subject=self.subject,
????????????message=self.message,
????????????html_message=self.html_message,
????????????from_email='from@example测试数据',
????????????recipient_list=[self.to_email],
????????????fail_silently=False,
????????)

让我们来看看经过这次改变后,注册服务的视图层是某种子:

import?logging

from?rest_framework.views?import?APIView
from?django.http?import?JsonResponse
from?django.core.mail?import?send_mail

from?users.models?import?User
from?users.mailers?import?BasicMailer

logger?=?logging.getLogger('django')


class?RegisterView(APIView):

????def?post(self,?request):
????????#?Run?validations
????????if?not?request.data:
????????????return?JsonResponse({'errors':?'User?data?must?be?provided'},?status=400)
????????if?User.objects.filter(email=request.data['email']).exists():
????????????return?JsonResponse({'errors':?'Email?already?in?use'},?status=400)
????????try:
????????????#?Create?new?user
????????????user?=?User.objects.create_user(email=request.data['email'].lower())
????????????user.set_password(request.data['password'])
????????????user.save()
????????????
????????????#?Send?welcome?email
????????????BasicMailer(to_email=user.email,?
????????????????????????subject='Welcome!',?
????????????????????????message='Hey?there!?Welcome?to?our?platform.',?
????????????????????????html_message='<p><strong>Het?there!</strong>?Welcome?to?our?platform.</p>').send_email()
????????????
????????????return?JsonResponse({'status':?'ok'})
????????except?Exception?as?e:
????????????logger.error('Error?at?%s',?'register?view',?exc_info=e)
????????????return?JsonResponse({'errors':?'Wrong?data?provided'},?status=400)

mailer子类

现在我们已经把所有的“邮件代码”移动到一个单独的地方,可以把它利用起来啦!这时候可以继续创建特定的mailer类,并让它们知道每次被调用时该发送什么内容。让我们创建一个mailer类用来在每次用户注册时进行调用,另一个mailer类用来发送订单的确认信息。

import?logging

from?django.conf?import?settings
from?django.core.mail?import?send_mail

from?users.models?import?User

logger?=?logging.getLogger('django')


class?BaseMailer():
????def?__init__(self,?to_email,?subject,?message,?html_message):
????????self.to_email?=?to_email
????????self.subject?=?subject
????????self.message?=?message
????????self.html_message?=?html_message

????def?send_email(self):
????????send_mail(
????????????subject=self.subject,
????????????message=self.message,
????????????html_message=self.html_message,
????????????from_email='from@example测试数据',
????????????recipient_list=[self.to_email],
????????????fail_silently=False,
????????)
????????
????????
class?RegisterMailer(BaseMailer):
????def?__init__(self,?to_email):
????????super().__init__(to_email,??
?????????????????????????subject='Welcome!',?
?????????????????????????message='Hey?there!?Welcome?to?our?platform.',?
?????????????????????????html_message='<p><strong>Het?there!</strong>?Welcome?to?our?platform.</p>')
????????

class?NewOrderMailer(BaseMailer):
????def?__init__(self,?to_email):
????????super().__init__(to_email,??
?????????????????????????subject='New?Order',?
?????????????????????????message='You?have?just?created?a?new?order',?
?????????????????????????html_message='<p>You?have?just?created?a?new?order.</p>')

这表明在不同的场景下集成邮件服务是非常简单的。你只需要构造一个基础的邮件类来进行实现,然后在子类中设置具体内容。

因为不用实现所有邮件相关的代码,所以现在我们注册服务的视图层看起来更加简明:

import?logging

from?rest_framework.views?import?APIView
from?django.http?import?JsonResponse
from?django.core.mail?import?send_mail

from?users.models?import?User
from?users.mailers?import?RegisterMailer

logger?=?logging.getLogger('django')


class?RegisterView(APIView):

????def?post(self,?request):
????????#?Run?validations
????????if?not?request.data:
????????????return?JsonResponse({'errors':?'User?data?must?be?provided'},?status=400)
????????if?User.objects.filter(email=request.data['email']).exists():
????????????return?JsonResponse({'errors':?'Email?already?in?use'},?status=400)
????????try:
????????????#?Create?new?user
????????????user?=?User.objects.create_user(email=request.data['email'].lower())
????????????user.set_password(request.data['password'])
????????????user.save()
????????????
????????????#?Send?welcome?email
????????????RegisterMailer(to_email=user.email).send_email()
????????????
????????????return?JsonResponse({'status':?'ok'})
????????except?Exception?as?e:
????????????logger.error('Error?at?%s',?'register?view',?exc_info=e)
????????????return?JsonResponse({'errors':?'Wrong?data?provided'},?status=400)

使用Sendgrid

假设我们必须使用正式的蟒库将我们的邮件服务后端迁移Sendgrid(一个用于交易和营销邮件的客户通信平台)。我们将不能再使用的Django的SEND_EMAIL方法,而且我们还不得不使用新库的语法。

嗯,但我们还是很幸运的地方!因为我们已经将所有与邮件管理相关的代码都放到了一个单独的地方,所以我们可以很轻松的进行这场比赛

import?logging

from?django.conf?import?settings
from?sendgrid?import?SendGridAPIClient,?Email,?Personalization
from?sendgrid.helpers.mail?import?Mail

from?users.models?import?User

logger?=?logging.getLogger('django')


class?BaseMailer():
????def?__init__(self,?email,?subject,?template_id):
????????self.mail?=?Mail()
????????self.subject?=?subject
????????self.template_id?=?template_id

????def?create_email(self):
????????self.mail.from_email?=?Email(settings.FROM_EMAIL)
????????self.mail.subject?=?self.subject
????????self.mail.template_id?=?self.template_id
????????personalization?=?Personalization()
????????personalization.add_to(Email(self.user.email))
????????self.mail.add_personalization(personalization)

????def?send_email(self):
????????self.create_email()
????????try:
????????????sg?=?SendGridAPIClient(settings.SENDGRID_API_KEY)
????????????sg.send(self.mail)
????????except?Exception?as?e:
????????????logger.error('Error?at?%s',?'mailer',?exc_info=e)
????????????

class?RegisterMailer(BaseMailer):
????def?__init__(self,?to_email):
????????super().__init__(to_email,?subject='Welcome!',?template_id=1234)

????????
class?NewOrderMailer(BaseMailer):
????def?__init__(self,?to_email):
????????super().__init__(to_email,?subject='New?Order',?template_id=5678)

请注意,您必须在配置文件中设置Sendgrid的api密钥,以及指定需要被使用的模板的ID,Sendrid会直接从自己的页面中根据这个ID来加载指定的html邮件模版。

太好了!这并不困难,而且我们不用去修改发送邮件的每一行代码。

现在让我们的步子再迈大一点。

根据域信息定制邮件内容

当然我们发送邮件的时候,有时候可能也会使用一些域信息来填充模板。比如说,如果在欢迎邮件里面能有新用户的名字,那肯定会显得更友好。Sendgrid 允许你在邮件模板中定义变量,这些变量将替换为从我们这里接收的实际信息。所以现在让我们来添加这部分数据吧!

import?logging

from?django.conf?import?settings
from?sendgrid?import?SendGridAPIClient,?Email,?Personalization
from?sendgrid.helpers.mail?import?Mail

from?users.models?import?User

logger?=?logging.getLogger('django')


class?BaseMailer():
????def?__init__(self,?email,?subject,?template_id):
????????self.mail?=?Mail()
????????self.user?=?User.objects.get(email=email)
????????self.subject?=?subject
????????self.template_id?=?template_id
????????self.substitutions?=?{
????????????'user_name':?self.user.first_name,
????????????'user_surname':?self.user.last_name
????????}


????def?create_email(self):
????????self.mail.from_email?=?Email(settings.FROM_EMAIL)
????????self.mail.subject?=?self.subject
????????self.mail.template_id?=?self.template_id
????????personalization?=?Personalization()
????????personalization.add_to(Email(self.user.email))
????????personalization.dynamic_template_data?=?self.substitutions
????????self.mail.add_personalization(personalization)

????def?send_email(self):
????????self.create_email()
????????try:
????????????sg?=?SendGridAPIClient(settings.SENDGRID_API_KEY)
????????????sg.send(self.mail)
????????except?Exception?as?e:
????????????logger.error('Error?at?%s',?'mailer',?exc_info=e)
????????????

class?RegisterMailer(BaseMailer):
????def?__init__(self,?to_email):
????????super().__init__(to_email,?subject='Welcome!',?template_id=1234)

????????
class?NewOrderMailer(BaseMailer):
????def?__init__(self,?to_email):
????????super().__init__(to_email,?subject='New?Order',?template_id=5678)

这里我看到的唯一一个问题是替换方案不那么灵活。很可能会发生的情况是,我们传递的数据可能是用户根据请求的上下文访问不到的。比如说,新的订单编号、重置密码的链接等等。这些变量参数可能很多,把它们作为命名参数传递可能会让代码变得比较脏乱。我们希望的是一个基于关键字,并且长度可变的参数列表,一般来说会被定义为 **kwargs,但在此我们命名它为 **substitutions,让这个表达会更形象:

import?loggingfrom?django.conf?import?settingsfrom?sendgrid?import?SendGridAPIClient,?Email,?Personalizationfrom?sendgrid.helpers.mail?import?Mailfrom?users.models?import?User

logger?=?logging.getLogger('django')class?BaseMailer():
????def?__init__(self,?email,?subject,?template_id,?**substitutions):
????????self.mail?=?Mail()
????????self.user?=?User.objects.get(email=email)
????????self.subject?=?subject
????????self.template_id?=?template_id
????????self.substitutions?=?{????????????'user_name':?self.user.first_name,????????????'user_surname':?self.user.last_name
????????}????????
????????for?key?in?substitutions:
????????????self.substitutions.update({key:?substitutions[key]})????def?create_email(self):
????????self.mail.from_email?=?Email(settings.FROM_EMAIL)
????????self.mail.subject?=?self.subject
????????self.mail.template_id?=?self.template_id
????????personalization?=?Personalization()
????????personalization.add_to(Email(self.user.email))
????????personalization.dynamic_template_data?=?self.substitutions
????????self.mail.add_personalization(personalization)????def?send_email(self):
????????self.create_email()????????try:
????????????sg?=?SendGridAPIClient(settings.SENDGRID_API_KEY)
????????????sg.send(self.mail)????????except?Exception?as?e:
????????????logger.error('Error?at?%s',?'mailer',?exc_info=e)????????????class?RegisterMailer(BaseMailer):
????def?__init__(self,?to_email,?**substitutions):
????????super().__init__(to_email,?subject='Welcome!',?template_id=1234,?**substitutions)????????class?NewOrderMailer(BaseMailer):
????def?__init__(self,?to_email):
????????super().__init__(to_email,?subject='New?Order',?template_id=5678,?**substitutions)

如果希望将额外信息传递给 mailer 类,就需要按如下编码:

NewOrderMailer(user.email,?order_id=instance.id).send_email()
PasswordResetMailer(user.email,?key=password_token.key).send_email()

总结

我们已经创建了一个灵活的 mailer 类,它将所有与电子邮件相关的代码封装在一个单独的地方,使代码维护变得更容易,并且还接收可变的上下文参数来填充电子邮件内容!一下子设计这整个方案肯定会很困难,但是我们一步一步的去实现就会简单得多,并且会在这个过程中受益良多。我鼓励你基于这个设计,继续开发邮件附件的功能!

查看更多关于Django发送邮件的详细内容...

  阅读:20次