点我查看本文集的说明及目录。
本项目相关内容( github传送 )包括:
实现过程:
CH1 创建一个博客应用
CH2 使用高级特性增强博客功能
CH3 扩展博客功能
项目总结及改进:
使用类视图实现 blog list 和 detail 视图
CH2 使用高级特性增强博客功能
前面一章,我们建立了一个简单的 blog 应用。现在我们使用高级特性对 blog 进行完善,增加通过 e-mail 分享文章、添加评论、添加文章标签、获取相似文章等功能。这一章我们将学习以下内容:
- 使用 Django 发送 e-mails
- 创建 forms 并在视图中处理它们
- 根据模型创建 forms
- 集成第三方应用
- 创建复杂的 QuerySets
通过e-mail分享文章
首先,我们实现通过发送邮件的方式分享文章。想一下如何通过上一章学到 views 、URL's 和 templates 实现这个功能。要实现通过 e-mail 发送邮件,我们需要:
- 为用户创建一个表单来填写他们的名字、e-mail、接收文章的 e-mail 和评论(可选);
- 在 views.py 文件中创建一个视图来处理 post 的数据并发送 e-mail ;
- 在 urls.py 文件中为新建的视图添加 URL 模式。
- 创建一个模板来展示表单。
使用Django创建表单
笔者注:
下面表述中 form 与表单意义相同。
我们从创建分享文章的表单开始。Django 内置的 form 框架帮助我们非常方便的创建表单。form 框架允许我们定义 form 的字段、指定字段展示方式、指定数据验证方法。Django form 框架还提供渲染表单和处理数据的方法。
Django提供两个类来创建表单:
- Form:帮助我们创建标准表单;
- ModelForm:帮助我们创建增加或者修改模型实例的表单。
首先,在 blog 应用的根目录新建一个名为 forms.py 的文件,并添加以下代码:
from django import forms
class EmailPostForm(forms.Form):
name = forms.CharField(max_length=25)
email = forms.EmailField()
to = forms.EmailField()
comments = forms.CharField(required=False, widget=forms.Textarea)
这是你的第一个 Django 表单。我们来看一下代码:创建继承 Form 的表单类,使用不同的字段对输入进行验证。
注意:
Forms 可以放在 Django 项目的任何位置,为了方便起见,我们将其放在每个应用的 forms.py 文件中。
name 字段是一个 CharField 。这种类型的字段渲染一个的 HTML元素。每一个字段都有默认的组件,这个组件决定 HTML 如何展示该字段。可以设置字段的 widget 属性覆盖默认的组件。在 comments 字段中,我们使用 Textarea 组件表示使用
HTML元素代替默认的
元素。
字段验证也依赖字段类型。例如,email 和 to 字段为 EmailField ,两个字段都需要有效地 e-mail 地址,否则字段验证将引发 forms.ValidationError 异常并且表单无法通过验证。表单验证还会考虑其他参数:我们定义了一个最大长度为 25 的 name 字段并将 comment 字段设置为 required=False 。表单验证时考虑这些参数。这个表单使用的字段类型只是 Django 表单字段的一小部分,所有的表单字段可以参考:https://docs.djangoproject.com/en/1.11/ref/forms/fields/。
在视图中处理表单
我们需要创建了一个新的视图来处理表单,并在表单成功提交时发送 e-mail 。编辑 blog 应用的 views.py 添加以下代码:
from .forms import EmailPostForm
def post_share(request, post_id):
# Retrieve post by id
post = get_object_or_404(Post, id=post_id, status='published')
if request.method == 'POST':
# Form was submitted
form = EmailPostForm(request.POST)
if form.is_valid():
# Form fields passed validation
cd = form.cleaned_data
# ... send email
else:
form = EmailPostForm()
return render(request, 'blog/post/share.html', {'post': post, 'form': form})
这个表单将实现以下功能:
- 定义 post_share 视图,该视图输入参数为:request 对象和 post_id 。
- 使用 get_object_or_404() 通过 id 获取文章并要求文章状态为 published 。
- 使用相同的视图展示初始表单和处理提交数据。使用 request 方法区分表单是否提交,如果 request 方法为 GET ,我们将展示一个空表单;如果 request 方法为 POST ,表单将被提交并且需要处理。因此我们使用request.method="POST" 来区分这两种情况。
下面是展示和处理表单的过程:
-
当使用 GET 方法请求视图时,我们创建一个新的表单实例(在模板中展示空的表单):
form=EmailPostForm()
-
用户填写表单并通过 POST 提交,我们在 POST 部分使用提交的数据创建了一个表单实例:
if request.method == 'POST': # Form was submitted form = EmailPostForm(request.POST)
使用 is_valid() 方法对提交的数据进行验证,这个方法对表单中的数据进行验证,如果数据均为有效数据,则会返回 Ture ,否则返回 False 。如果验证为 False 我们可以访问 form.errors 查看错误列表:
如果表单没有通过验证,使用提交的数据再次渲染表单,并且在模板中显示验证错误。
-
如果表单通过验证,通过 form.cleaned_data 访问数据,这个属性为表单字段名和值的字典。
注意:
如果表单字段没有验证,cleaned_data 只包含通过验证的字段。
现在我们需要学习如何使用 Django 发送邮件了。
使用Django发送邮件
使用 Django 发送邮件非常简单。首先,我们需要一个本地 SMTP 服务器或者在项目 setting.py 中配置一个外部 SMTP 服务器:
EMAIL_HOST: SMTP 服务器主机。默认为 localhost
EMAIL_PORT: SMTP 服务器端口。默认为 25
EMAIL_HOST_USER: SMTP 服务器的用户名
EMAIL_HOST_PASSWORD: SMTP 服务器的密码
EMAIL_USE_TLS: 是否使用 TLS 安全连接;
EMAIL_USE_SSL: 是否使用隐式 SSL 安全连接。
如果没有本地 SMTP 服务器,可以使用 e-mail 服务商提供的 SMTP 服务器。下面的简单配置是使用 hotmail 服务器发送 e-mail 的配置(https://outlook.live.com/owa/?path=/options/popandimap):
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'your_account@gmail.com'
EMAIL_HOST_PASSWORD = 'your_password'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
笔者注:
原文使用的是 Gmail,由于国内无法使用 Gmail,因此使用 hotmail 代替。
配置中,EMAIL_USE_TLS 和 EMAIL_USE_SSL 的默认设置都为False,需要配置其中一个为 True ,但是不能两个都设置为True。一般端口 587 对应 TLS ,端口 465 对应 SSL(加强 TSL )。
在 teminal 中,跳转到项目根目录并输入命令 python manage.py shell 打开 Python shell 并发送邮件:
from django.core.mail import send_mail
>>> send_mail('Django mail', 'This e-mail was sent with Django.', 'your_account@gmail.com', ['your_account@gmail.com'], fail_silently=False)
send_mail() 参数包括主题、消息、发送者、接收者列表,通过设置 fail_silently=False 选项确定邮件没有正确发送时是否引发异常。如果send_mail 的输出为 1 ,那么邮件就正常发送了。如果 setting.py 设置 google web服务器,需要正常访问以下网址:https://www.google.com/settings/security/lesssecureapps。
笔者注:
发送邮件测试
国内无法访问https://www.google.com/settings/security/lesssecureapps。因此,测试了 hotmail邮箱和163邮箱:
测试环境:python2.7、Django1.11
hotmail邮箱
下面的简单配置是使用 hotmail 服务器发送 e-mail 的配置(https://outlook.live.com/owa/?path=/options/popandimap):
EMAIL_HOST = 'smtp-mail.outlook.com' EMAIL_HOST_USER = 'your_account@hotmail.com' EMAIL_HOST_PASSWORD = 'your_password' EMAIL_PORT = 587 EMAIL_USE_TLS = True
在 teminal 中,跳转到项目根目录并输入命令 python manage.py shell 打开 Python shell 并发送邮件:
from django.core.mail import send_mail >>> send_mail('Django mail', 'This e-mail was sent with Django.', 'your_account@hotmail.com', ['your_account@163.com'], fail_silently=False)
如果 send_mail 第三个参数 your_account@hotmail.com 与 EMAIL_HOST_USER 一致,邮件正常发送。但是如果接收邮件只有阿里云企业邮箱,该邮箱没有返回,需要在程序中增加时间限制,否则邮件发送正常但是程序会长时间等待接收反馈信息而无法执行其他命令。
如果 send_mail 第三个参数与 EMAIL_HOST_USER 不一致,会引发 SMTPDataError 异常,无法发送邮件。
163邮箱
EMAIL_HOST = 'smtp.163.com' EMAIL_HOST_USER = 'your_account@163.com' EMAIL_HOST_PASSWORD = 'your_auth_code' #邮箱的授权码而非密码 EMAIL_PORT = 465 EMAIL_USE_SSL = True
在 teminal 中,跳转到项目根目录并输入命令 python manage.py shell 打开 Python shell 并发送邮件:
from django.core.mail import send_mail >>> send_mail('Django mail', 'This e-mail was sent with Django.', 'your_account@hotmail.com', ['your_account@163.com'], fail_silently=False)
注意,这里的 password 不是邮箱密码,而是[邮箱]-[设置]-[POP3/SMTP/IMAP]中设置的授权码。
如果 send_mail 第三个参数与 EMAIL_HOST_USER 一致,引发 SMTPDataError: (554, 'DT:SPM 163 smtp11,D8CowADnvQK5UwFa6C2ZAw--.46120S2 1510036409,please see http://mail.163.com/help/help_spam_16.htm?ip=120.194.143.53&hostid=smtp11&time=1510036409')异常,http://mail.163.com/help/help_spam_16.htm?ip=120.194.143.53&hostid=smtp11&time=1510036409的对应内容为该邮件被视为垃圾邮件,更改主题与内容后仍无效。需要进一步了解 163 判断垃圾邮件的依据。
如果 send_mail 第三个参数为 hotmail 邮箱,邮件无法发送,引 发SMTPSenderRefused: (553, 'Mail from must equal authorized user') 异常。即发送信息邮箱必须与授权邮箱一致。
发送邮件测试总结
- 测试环境:python2.7+Django1.11。
- settings.py 尽量配置 hotmail 邮箱;
- send_mail 中的发送邮箱最好与settings.py 中的授权邮箱一致。
现在,在视图中将添加发送邮件功能,将 blog 应用 views.py 中的 post_share 视图更改为:
from .forms import EmailPostForm
from django.core.mail import send_mail
def post_share(request, post_id):
# Retrieve post by id
post = get_object_or_404(Post, id=post_id, status='published')
sent = False
if request.method == 'POST':
# Form was submitted
form = EmailPostForm(request.POST)
if form.is_valid():
# Form fields passed validation
cd = form.cleaned_data
# send email
post_url = request.build_absolute_uri(post.get_absolute_url())
subject = '{}({})recommends you read "{}"'.format(cd['name'],
cd['email'],
post.title)
message = 'read"{}" at {} \n\n\'s comments:{}'.format(post.title,
post_url,
cd['name'],
cd[
'comments'])
send_mail(subject, message, cd['email'], [cd['to']])
sent = True
else:
form = EmailPostForm()
return render(request, 'blog/post/share.html',
{'post': post, 'form': form, 'sent': sent})
这里定义了 sent 变量,邮件发送成功时 sent 设为 True 。我们将在模板中使用这个变量,当表单正确提交且邮件发送成功时显示成功信息。这里使用 get_absolute_url() 方法获取在文章的链接并使用 request.build_absolute_uri() 来封装这个函数从而构建包含 http 的完整 URL 。使用验证成功的表单的cleaned_data 作为邮件的主题和内容,然后将邮件发送到表单中 to 一栏中输入的地址。
现在视图完成了,我们为其添加 URL 模式,打开 blog 应用下的 urls.py 文件,添加以下内容:
urlpatterns = [
# ...
url(r'^(?P\d+)/share/$', views.post_share,
name='post_share'),
]
在模板中渲染表单
创建完表单、完成视图并添加 URL 模式之后,还需要为视图添加模板。在 blog/templates/blog/post 目录下创建名为 share.html 的文件,添加以下内容:
{% extends "blog/base.html" %}
{% block title %}Share a post{% endblock %}
{% block content %}
{% if sent %}
E-mail successfully sent
"{{ post.title }}" was successfully sent to {{ form.to.value }}.
{% else %}
Share "{{ post.title }}" by e-mail
{% endif %}
{% endblock %}
笔者注:
由于{{ form.to }} 将渲染为 元素,这里使用了 {{ form.to.value }}代替了原文的 {{ form.to }}。
这是展示表单的模板,邮件发送成功时模板会显示成功信息。我们创建了使用 POST 提交的表单:
然后添加了表单实例,通过 as_p 方法告诉 Django 将表单字段渲染为 HTML 的
元素(我们还可以通过as_tabl e方法将字段渲染为
Comment {{ forloop.counter }} by {{ comment.name }}{{ comment.created }}
{{ comment.body|linebreaks }}