Django
Django邮件
在用户注册时使用了邮件,现在修改注册验证机制。注册时,系统发送验证邮件到提交的邮件地址,验证后,注册才算成功。通过邮箱可以进行修改密码操作。
配置相关参数
根据官网的设置,使用Django需要在 setting.py 中进行一些设置。Django默认使用SMTP传输协议,为了使用其后台,添加:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
同时要设置的参数:
EMAIL_HOST
:使用的SMTP邮件服务器地址;EMAIL_PORT
:服务器的端口;EMAIL_HOST_USER
:邮箱用户名;EMAIL_HOST_PASSWORD
:邮箱用户密码;EMAIL_USE_TLS
:是否启用TLS
协议;EMAIL_USE_SSL
:SSL
协议,使用的端口为465(与EMAIL_USE_TLS
互斥);EMAIL_TIMEOUT
:指定阻塞操作的超时时间(秒)。EMAIL_SSL_KEYFILE
:启用SSl
时,可指定用于ssl链接的私钥文件EMAIL_SSL_CERTFILE
:启用SSl
时,可指定用于ssl链接的证书文件
发送邮件
send_mail()
Django 提供了send_mail()
函数,可以轻松的进行邮件发送。
send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None, connection=None, html_message=None)
subject
:邮件的主题;message
:邮件内容;from_email
:邮件发送人"xxx@xx"recipient_list
:邮件接收者[email_address1, email_address2,…]fail_silently
:设置为False时,发送邮件出错时会报错;auth_user
和auth_password
:SMTP验证需要的用户名和密码,没有设置时使用setting中设置的值;connection
:指定一个邮件发送连接email-sending connection
,没有设置时会自动创建一个;html_message
:html格式的内容;
修改注册视图函数在注册时发送验证邮件:
authenticated_user = authenticate(username=new_user.username,
password=request.POST.get('password1', ""))
send_mail('注册', '验证信息', EMAIL_HOST_USER, [new_user.email,], fail_silently=False)
login(request, authenticated_user)
return HttpResponseRedirect(reverse('learning_logs:index'))
查看邮件信息
send_mass_mail()
send_mass_mail()与send_mail()类似,但它可以一次性发送出多封邮件,使用方法:
message1 = ('Subject here', 'Here is the message', 'from@example.com', ['first@example.com', 'other@example.com'])
message2 = ('Another Subject', 'Here is another message', 'from@example.com', ['second@test.com'])
send_mass_mail((message1, message2), fail_silently=False)
两者区别:send_mail()
在每次执行时都会建立一个邮件发送连接(email-sending connection
);而send_mass_mail
在发送多封邮件时会使用一个连接,这使得它在发送多封邮件时比send_mail
更有效率。
自定义异步任务发送邮件
在上述注册函数中邮件发送是在主进程中,点击注册后会等待一段时间。在用户邮箱注册的时候, 在发送邮件的时候可以先把"已经发送激活邮件到邮箱"返回给用户, 同时把邮件发送任务提交到异步处理线程中。
myemail.py
from django.core.mail import send_mail as core_send_mail
from django.core.mail import EmailMultiAlternatives
import threading
class MyEmailThread(threading.Thread):
"""多线程,发送邮件"""
def __init__(self, subject, body, from_email, recipient_list, fail_silently, html_message):
threading.Thread.__init__(self)
self.subject = subject
self.body = body
self.recipient_list = recipient_list
self.from_email = from_email
self.fail_silently = fail_silently
self.html_message = html_message
# 发送邮件
def run(self):
mail = EmailMultiAlternatives(self.subject, self.body, self.from_email, self.recipient_list)
if self.html_message :
mail.attach_alternative(self.html_message, 'text/html')
return mail.send(self.fail_silently)
# 创建线程 start 启动线程活动,会调用run方法
def my_send_mail(subject, body, from_email, recipient_list, fail_silently=False, html_message=None, *args, **kwargs):
MyEmailThread(subject, body, from_email, recipient_list, fail_silently, html_message).start()
根据python多线程和send_email
的源码,将发送邮件的任务放在一个新的线程中进行。
调用自定义的邮件发送:
from .my_email import my_send_mail
# ...
my_send_mail('注册', '验证',EMAIL_HOST_USER, [new_user.email])
点击注册后立即跳转到主页,邮件接受的速度也大幅提升。
Celery异步任务框架发送邮件
使用celery异步任务处理框架,进行异步邮件发送,详细见Celery学习
安装djcelery:pip install django-celery
,安装并使用Redis作为中间件broker和结果后端backend:pip install redis
在setting下加入
import djcelery
djcelery.setup_loader()
设置Celery的加载路径,并添加djcelery
到INSTALLED_APPS
中
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 创建的应用
'learning_logs',
'users',
'djcelery',
'bootstrap4',
]
同样的Celery的一些参数配置,可以放到setting下而不用单独的配置文件:
# Celery设置
# 时区
CELERY_TIMEZONE = 'Asia/Shanghai'
# 中间件
BROKER_URL= 'redis://password@localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://password@localhost:6379/0'
在需要异步任务的应用下添加tasks.py
,内部是要执行的异步任务
from django.core.mail import send_mail,send_mass_mail, EmailMessage
from celery import task
import os
@task
def send_register_email(message, to):
send_mail('注册', message,
os.environ.get('EMAIL_HOST_USER'),
[to, ], fail_silently=False)
在需要调用异步任务时,通过.delay()
(apply_async
)方法让发送邮件的任务异步执行
from users.tasks import send_register_email
...
authenticated_user = authenticate(username=new_user.username,
password=request.POST.get('password1', ""))
send_register_email.delay("注册",new_user.email)
login(request, authenticated_user)
在运行时先运行项目python manage.py runserver
,再运行Celerypython manage.py celery worker -l info
启动celery的任务执行单元worker
。
-------------- celery@ulysses v3.1.26.post2 (Cipater)
---- **** -----
--- * *** * -- Linux-4.15.0-36-generic-x86_64-with-Ubuntu-18.04-bionic
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app: default:0x7fec39c32208 (djcelery.loaders.DjangoLoader)
- ** ---------- .> transport: redis://localhost:6379/0
- ** ---------- .> results: redis://localhost:6379/0
- *** --- * --- .> concurrency: 4 (prefork)
-- ******* ----
--- ***** ----- [queues]
-------------- .> celery exchange=celery(direct) key=celery
[tasks]
. users.tasks.send_register_email
[2018-10-29 10:32:47,752: INFO/MainProcess] Connected to redis://localhost:6379/0
[2018-10-29 10:32:47,760: INFO/MainProcess] mingle: searching for neighbors
[2018-10-29 10:32:48,773: INFO/MainProcess] mingle: all alone
这次注册完成后,直接进入了主界面,在worker后台可以看到邮件发送任务在异步执行
[2018-10-29 10:46:17,772: INFO/MainProcess] Received task: users.tasks.send_register_email[39e827a5-13dd-4efd-beba-6612061fde2f]
[2018-10-29 10:46:30,005: INFO/MainProcess] Task users.tasks.send_register_email[39e827a5-13dd-4efd-beba-6612061fde2f] succeeded in 12.232193824000205s: None
上面使用Redis作为CELERY_RESULT_BACKEND
,也可以使用Django ORM/Cache作为任务结果
设置结果后端CELERY_RESULT_BACKEND = 'django-db'
或CELERY_RESULT_BACKEND = 'django-cache'
celery数据迁移:python manage.py migrate djcelery
HTML格式邮件
在邮件的正文中添加html格式的文本并添加附件。
@task
def send_register_email(user):
subject, from_email, to = "注册", os.environ.get('EMAIL_HOST_USER'), \
user.email
text_content = "{{ user.username }},你好\
欢迎来到 Ulysses\
为了验证您的账户,请点击以下链接进行验证\
链接\
Ulysses\
请勿回复此邮件"
html_content = f"<p>{user.username}, 你好</p>\
<p>欢迎来到 <b>Ulysses</b>!</p>\
<p>为了验证您的账户,请点击进行验证</p>\
<p>或者您可以在浏览器被输入以下内容:</p>\
<p>链接</p>\
<p>Ulysses</p>\
<p><small>请勿回复此邮件</small></p>"
email = EmailMultiAlternatives(subject=subject, body=text_content, from_email=from_email, to=[to])
email.attach_alternative(html_content, 'text/html')
# email.attach_alternative()
# 添加附件
email.attach_file('users/templates/email/confirm.html', 'text/plain')
email.attach_file('users/templates/email/confirm.txt', 'text/html')
email.send()
EmailMultiAlternatives
类是EmailMessage
的一个子类(send_mail
就是对EmailMessage
的简单包装), 通过attach_alternative
方法可以在邮件正文中添加其他类型的内容,代替原有的text/plain
类型body
。若邮箱不支持接受html格式文本,就会显示默认text/plain
格式文本。attach_file
方法可以添加邮件附件,若为MIME
类型需要指明。
Django消息
在Web应用程序中,经常需要在处理表单或其他类型的用户输入后向用户显示一次性通知消息(也称为“flash message”)。Django提供了支持cookie和会话(session)的消息框架,消息框架允许您将消息临时存储在一个请求中并检索它们以便在后续请求(通常是下一个请求)中显示。 每条消息都标记有确定其优先级的特定级别(例如,info
,warning
,error
)。
message通过中间件和上下文处理器才能实施运行,所以在setting中:
INSTALLED_APPS
要包含message应用'django.contrib.messages'
;MIDDLEWARE
列表中需要有'django.contrib.sessions.middleware.SessionMiddleware'
和'django.contrib.messages.middleware.MessageMiddleware'
- 在
TEMPLATES
模板的'OPTIONS'
配置里要有处理message的上下文处理器'django.contrib.messages.context_processors.messages'
,否则在HTM模板中找不到message;
消息的存储
Django可以使用不同后台来存储产生的临时消息, 在django.contrib.messages
下有3类可以保存消息:
- class storage.session.SessionStorage:存储请求会话内的所有消息。 因此它需要django.contrib.sessions应用;
- class storage.cookie.CookieStorage:将消息存储在cookie中(名为
messages
)会使用hash加密,超过2048字节丢弃旧的消息,能在不同请求中使用消息; - class storage.fallback.FallbackStorage:首先使用CookieStorage,若单一的cookie无法满足需求会使用SessionStorage,这也是Django默认的消息存储机制;
这3类都提供了_get
和_store
来读取或保存消息。若要使用其余两种方式进行进行消息存取,在setting中指定存储方式:MESSAGE_STORAGE = 'django.contrib.messages.storage.xxxx'
。
消息的级别
与Python的logging中的通知级别类似,Django默认的消息可分级为:DEBUG
,INFO
, SUCCESS
,WARNING
和 ERROR
;对应的在html模板中消息的标签:debug
,info
,success
,warning
和error
。
添加和显示消息
from django.contrib import messages
messages.add_message(request, messages.INFO, 'Welcome')
messages.error(request, 'Email box full', extra_tags='email')
添加消息,使用add_message(request, level, message, extra_tags='', fail_silently=False)
方法,或者使用确定了消息级别的函数来生成消息:message.errorr()
,message.info()
等。extra_tags
可以添加额外的标签,提供给模板进行处理。
在模板中显示消息:
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}Important: {% endif %}
{{ message }}
</li>
{% endfor %}
</ul>
{% endif %}
在模板中可以使用直接使用messages
以及它的属性。
在网页上可以看到生成的消息提示。
使用Bootstrap样式
{% if messages %}
{% for message in messages %}
{# 原生 Bootstrap #}
<div class="alert {% if message.tags %} alert-{{ message.tags }}{% endif %}" role="alert">
{{ message }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
{% endfor %}
{% endif %}
使用一对<div class="alert alert-<type>" role="alert"></div>
标签声名一个消息,消息的长度不限。可以添加一个按钮(dismiss button)来关闭消息提示栏,需要给其添加data-dismiss="alert"
属性,显示效果:
可以使用django-bootstrap 的bootstrap_alert
标签代替使用原生的Bootstrap:
{% if messages %}
{% for message in messages %}
{# django-bootstrap #}
<div>
{% bootstrap_alert message alert_type=message.tags %}
</div>
{% endfor %}
{% endif %}
自定义消息级别
Bootstrap的alert类型有primary
,secondary
,success
,warning
,info
,light
和dark
共7种,而实质上Django的消息的级别(level
)就是个整数,因此可以自定义消息的级别。
django.contrib.messages
中的默认设置
DEBUG = 10
INFO = 20
SUCCESS = 25
WARNING = 30
ERROR = 40
DEFAULT_TAGS = {
DEBUG: 'debug',
INFO: 'info',
SUCCESS: 'success',
WARNING: 'warning',
ERROR: 'error',
}
DEFAULT_LEVELS = {
'DEBUG': DEBUG,
'INFO': INFO,
'SUCCESS': SUCCESS,
'WARNING': WARNING,
'ERROR': ERROR,
}
在setting中自定义消息级别和消息标签:
from django.contrib.messages import constants as message_constants
LIGHT = 2
DARK = 4
PRIMARY = 6
SECONDARY = 8
DANGER = 50
MESSAGE_LEVEL = LIGHT
MESSAGE_TAGS = {
LIGHT: 'light',
DARK: 'dark',
PRIMARY: 'primary',
SECONDARY: 'secondary',
DANGER: 'danger'
}
MESSAGE_LEVEL
设置了显示消息的最低级别,低于(数值小于)此级别的消息不会显示。
使用自定义消息级别:
from django_ulysses.settings import DANGER, PRIMARY
...
messages.add_message(request, PRIMARY, 'primary')
messages.add_message(request, DANGER, 'danger')
显示的效果:
在注册后,给予一条提示,告知验证邮件已发送:
messages.add_message(request, messages.INFO, '邮件已发送,请查收')