在上一章节,创建了一个基本的blog应用.现在,将应用转入具有高级特性的全功能blog,例如email分享post,添加评论,文章标签,接收相似文章.在这一章节,将会学习到如下内容:
- 使用Django发送邮件
- 视图中创建并处理表单
- 使用models创建表单
- 整合第三方应用
- 创建混合querysets
使用Email分享posts
首先,我们将允许用户使用Email分享posts.花一点点时间想一下如何使用上一章节学习的视图,URLS,模板创建这个功能.现在,检查允许用户发送邮件需要哪些东西.你将做如下的操作:
- 创建一个from允许用户填写用户名,email,email接收者,评论.
- 创建一个view处理数据和发送邮件
- 为上面的view添加一个url匹配
- 创建一个template显示from表单
在Django中创建froms表单
让我们来创建分享表单.Django内置表单框架允许你轻松创建表单.表单框架允许你定义表单字段,指定如何显示,标识如何验证数据.Django表单框架灵活的渲染表单和处理数据.
Django使用两个类来创建表单:
- from: 创建基本表单
- ModelForm: 创建数据绑定表单
首先,我们在blog应用目录创建from.py文件:
1 | from django import forms |
这是我们第一个表单.分析一下代码内容.我们继承Form类创建一个表单.使用了不同的字段类型,Django将相应的验证这些字段.
表单可以存在于Django工程的任何地方,最好在每个应用的目录下存放在form.py文件中
name字段是字符串字段.这个字段渲染为一个HTML元素
1 | <input type="text"> |
每个字段都有一个默认的微件决定字段在HTML中如何渲染.comments字段我们使用了一个Textarea的微件在HTML显示为
1 | <textarea> |
元素.
字段验证同样信赖字段类型.例如,email和to字段使用EmailField类型.两个字段都需要验证email地址,否则将提示forms.ValidationError错误.其它参数也将被带入验证:我们为name定义了最大长度25字符,comments字段使用required=False表示不是必须的.所有这些都存在字段验证.这里只使用了Django表单字段的一部分,所有表单字段可查阅:https://docs.djangoproject.com/en/2.0/ref/forms/fields/
在视图中处理表单
你需要创建一个新的视图处理当提交成功时表单发送email.编辑应用下的views.py文件:
1 | # 导入表单 |
视图处理流程如下:
1 创建post_share视图,带入request和post_id参数
2 使用get_object_or_404()快捷方法接收post对象,根据后面的参数post_id及status=published.
3 使用同一视图显示初始表单及处理表单提交数据.区别是提交还是显示基于request的方法,提交是POST.如果是GET方法,显示表单;如果是POST方法,表单是提交,然后需要view做处理.request.method == ‘POST’是区分两种方式的关键.
下面是处理显示及处理表单数据:
1 当表单是初始状态或接收GET请求,我们创建一个新表单实例在模板中来显示空白表单.
2 当用户输入数据点击提交后接收为POST,然后我们使用提交的数据创建包含request.POST的表单实例:
3 在这个过程中,我们验证提交的数据使用form.is_vaild()方法.这个方法验证表单数据正确返回True.错误返回False.可以使用form.errors查看验证错误列表.
4 如果表单不正确,再次渲染表单模板并显示验证错误.
5 如果表单正确,我们通过form.cleaned_data接收验证过的数据.这个属性是一个表单字段和表单字段值的一个字典.
如果表单数据不正确,cleaned_data将只包含通过验证的数据.
现在,让我学习如何在Django中发送邮件来把事情融合.
使用django发送邮件
Django发送邮件是相当直接的.首先,你需要有一个本地SMTP服务器或在settings.py中定义一个外界的SMTP服务器:
- EMAIL_HOST: smtp服务器地址,默认是localhost
- EMAIL_PORT: smtp端口,默认25
- EMAIL_HOST_USER: 用户名
- EMAIL_HOST_PASSWORD: 密码
- EMAIL_USE_TLS: 是否使用TLS安全连接
- EMAIL_USE_SSL: 是否使用SSL安全连接
如果你不能使用SMTP服务器,你可以告诉Django输出email到终端,添加如下配置:
1 | # For Email |
这样配置后,Django将输入email到终端.这在没有SMTP服务器的情况下测试非常有用.
如果你想发送邮件,但没有本地SMTP服务器,你可以使用SMTP服务商.下面是使用GMAIL的配置:
1 | EMAIL_HOST = 'smtp.gmail.com' |
运行python manage.py shell打开终端,然后发送email:
1 | >>> from django.core.mail import send_mail |
send_mail()功能带入标题,信息,发送者,一个接收者列表作为必选参数.可选参数fail_silently=False,我们告知它如果不能正确发送触发一个错误.
如果发送email由GMAIL提供,必需允许低安全应用访问https://myaccount.google.com/lesssecureapps.
现在,我们添加发邮件功能到视图中.
修改post_share视图:
1 | # 发送邮件 |
我们宣告一个sent变量,邮件发送成功后设置为True.如果发送成功,我们将稍后在模板中使用这个变量来显示成功消息.因为必需在邮件正文中包含post的链接,我们使用Post模型中定义的get_absolute_url()方法.我们使用这个地址带入request.build_absolute_url()方法来创建包含HTTP结构及主机名的完整的url.
然后我们使用cleaned_data创建邮件标题,信息;最后发送这个邮件到to字段的地址.
现在视图已经完整,但还是需要添加url匹配规则.打开urls.py修改:
1 | path('<int:post_id>/share/', views.post_share, name='post_share'), |
在模板中渲染表单
创建表单,编写视图,添加URL匹配后,我们还缺少模板.创建一个新文件blog/templates/blog/post/share.html,添加下面的代码:
1 | {% extends 'blog/base.html' %} |
如果已经发送,这个模板将显示一个成功消息.我们创建了一个html元素,POST方法提交.
我们使用as_p()方法告诉Django渲染表单字段在HTML元素
.我们也可以使用as_ul或as_table.如果我们希望渲染每个字段,我们可以迭代每个字段:
1 | {% for field in form %} |
1 | {% csrf_token %} |
模板标记隐含字段包含一个自动生成的token防止 cross-site request forgery(CSRF)攻击.那些包含恶意网站或不希望的执行程序在你的站点上.在这里可以找到更多说明https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
处理标记生成一个隐含字段如下:
1 | <input type='hidden' name='csrfmiddlewaretoken' |
修改blog/post/detail.html模板添加分享链接:
1 | {{ post.body|linebreaks }} |
在这里我们使用Django提供的url动态创建链接.使用命令空间blog和url名称post_share,然后传递post ID作为参数来创建绝对路径的URL.
现在可以启动开发服务器,查看页面运行情况了.
如果输入字段类型不对,会提示错误.
创建一个评论系统
现在,我们将创建一个评论系统,读者能够评论文章.为了实现这个功能,我们将做如下事情:
1 创建一个model存储comments
2 创建一个form验证并提交comments
3 创建一个view处理from并存储commnets到model数据库
4 编辑post detail模板显示comments列表和添加新comment表单
首先,创建一个model存储评论.打开models.py文件:
1 | class Comment(models.Model): |
我们的Comment model,包含外键ForeignKey辅助访问一个post的comment.这里定义了一个多对一关系,因为每个comment只能评论在一个post上,每个post可以有多个comments.related_name属性允许我们为从关联对象回到关系所使用的属性命名.定义后,我们可以使用comment.post接收comment对象的post,接收post的所有comments使用post.comments.all().如果你不定义related_name属性.,Django将使用model的小写加_set作为关联对象post回指的管理器.
查看更多多对一关系https://docs.djangoproject.com/en/2.0/topics/db/examples/many_to_one/
我们创建了一个active布尔字段为了手工关闭不适合的评论.我们使用created创建时间做为排默认排序.
创建完Commnet model后并没有同步到数据库.使用Django迁移命令.
1 | (django2byExample) haotianfei@tianfei-opensuse:~/PycharmProjects/Django2byExample> python manage.py makemigrations blog |
现在,为了通过一个简单的接口管理comments我们添加新model到管理站点.修改admin.py
1 | from blog.models import Comment |
运行开发服务器,执行python manage.py runserver命令,使用浏览器打开http://127.0.0.1:8000/admin,你将会看到新的model已经在Blog中了.
现在model已经注册到管理站点,我们可以使用一个简单的接口管理Comment实例了
从models创建froms
我们需要创建一个能让用户评论文章的表单.Django有两个基础类可以创建表单,Form和ModelForm.我们使用Form在上面创建用户email分享.在这一部分,因为要从Comment模型创建一个动态表单使用ModelForm.编辑forms.py,添加下面的代码:
1 | from .models import Comment |
从model创建form,只需要在Meta类中指出从哪个model创建.Django自己会跟model交互然后动态的创建.每个model字段类型都对应有默认的表单字段.定义model类型是为了解释表单验证.Django为model中每一个字段创建表单字段.但可以使用fields列表明确的告知框架你想包含在表单中的字段,或使用exclude定义哪些字段你想要排除.在这个CommentForm表单,我们将使用name,email,body字段,因为我们用户只需要填入这些.
在视图中处理ModelForm
我们将在post的详细页面视图中初始化一个表单并处理,这样可以保持简洁.编辑views.py文件:
1 | # ModelForm |
我们使用post的详细页显示评论.首先使用一个QuerySet接收所有post评论,命名为comments,使用Commnet关系模型关联属性对象的管理器接收对象.
我们同时使用相同的视图来主我们的用户添加新的评论.初始化new_comment变量为None.我们将在创建新的comment时使用这个变量.如果是request.GET请求使用comment_form创建一个初始表单.如果请求是POST,我们将使用提交数据和变量初始表单并使用is_vaild()验证方法.如果表单正确,将产生以下动作:
1 使用表单save()方法创建新Comment对象,附给new_commnet变量.save()方法创建一个表单链接新的模型实例,并存储到数据库.如果调用时使用commit=False,创建实例但不保存到数据库.这可以在保存之间修改对象,我们需要修改数据.save只在ModelForm中有用,Form无效.因为Form没有关联model
2 关联post到刚刚创建的commnet.这样就指定了新的评论到post.
3 最后,调用save()保存新的comment到数据库
现在视图已经可以显示并处理新的评论.
添加评论到文章详细页模板
我们已经创建post管理comment的功能.现在,需要对post/detail.html模板做下面的事情:
- 显示一篇post的总commnet数量
- 显示commnets列表
- 显示一个新commnet表单
首先,添加评论总数.
1 | <span> |
在模板中使用Django ORM,执行QuerySet comments.count.Django模板语言调用方法不会使用复数.with标签允许附加值到一个新的变量.pluralize模板过滤器根据commnets的值来显示复数后辍.模板过滤器带入变量值做为输入,然后返回一个混合后的值.第三章节将详细介绍过滤器.
如果结果不为1,pluralize过滤器将返回字母s.Django包含完美的模板标记和过滤器帮助显示你想要的信息.
现在,列出评论.代码如下:
1 | {% for comment in comments %} |
我们使用for模板标签循环commnets.如果评论为空,显示一条消息,提示用户现在还没有评论.列举评论使用forloop.counter变量,包含整数循环计数.然后我们显示评论用户名,日期,评论内容.
最后,需要渲染一个表单或显示成功提交消息.
1 | <span> |
代码相当简洁直接.如果new_comment对象存在,显示一条成功消息.否则,显示post表单并包含csrf token.
可以通过编辑http://127.0.0.1/admin/blog/comment active字段不显示不合适的评论,从文章评论中屏蔽它们.
添加标签功能
完成实施comment系统后,接下来创建post tag.工程将整合一个第三方Django tag应用。django-tag模块是的利用程序,一个主要提供一个Tag model和轻松标签到任何model一个管理器.可以到https://github.com/alex/django-taggit查看源码。
首先,执行以下命令使用pip安装django-taggit。
1 | pip install django-taggit |
打开settings.py文件添加taggit到INSTALLED_APPS:
1 | INSTALLED_APPS = [ |
打开models.py文件添加django-taggit提供的TaggableManager管理器到Post model:
1 | from taggit.managers import TaggableManager |
tags管理器将允许从Post对象中添加,接收,移除tag。
运行下面的命令创建migration。
1 | (django2byExample) haotianfei@tianfei-opensuse:~/PycharmProjects/Django2byExample> python manage.py makemigrations blog |
现在数据库已经准备好使用django-taggit models。让我们学习如何来使用tags管理器。打开终端python manage.py shell.首先,接收一个post;然后添加tags到这个post;检查是否已经成功添加。最后,移除一个tag然后再次检查。
1 | >>> from blog.models import Post |
是不是很容易?运行runserver命令,打开admin网站查看看tag对象。
然后到post对象上查看tag字段,还可以编辑它。
下面将在编辑post页面显示tags。
打开blog/post/list.html template添加下面的html代码:
1 | <p class="tags">Tags: {{ post.tags.all|join:", " }}</p> |
接下来,我们将编辑post_list view让用户可以列出所有打了指定tag的posts。编辑view.py,从django-taggit import Tag model,通过修改post_list view使用可选的tag_slug过滤一个tag的所有posts:
1 | # tag,tag标签复用了post_list模板 |
post_list veiw作用流程如下:
1 带入一个可选的参数tag_slug(默认为None).这个参数来自url.
2 在view内部,创建一个初始QuerySet,接收所有发布的posts,但如果有一个tag slug参数,我们将使用get_object_or_404快捷方式带入slug取得Tag对象.
3 然后,我们过滤包含tag的posts列表.这个一个多对多关联,所有使用包含过滤,在这里,只包含一个元素.
QuerySet只在我们解析模板时才循环post列表.
最后,修改view底部的render()函数将tag变量传递到模板.
1 | return render(request, |
编辑urls.py,注释掉PostListView,去掉post_list view的注释,添加tag
1 | path('tag/<slug:tag_slug>/', views.post_list, name='post_list_by_tag'), |
修改post_list.html加入tag判断
1 | {% for post in posts %} |
为每个tag标签添加超链接
1 | <p class="tags"> |
接收相似文章
在post_detail view中计算相关性,在detail.html中列举:
1 | post_tags_ids = post.tags.values_list('id', flat=True) |
1 |
|