增强博客功能
Posted 一个处女座的测试
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了增强博客功能相关的知识,希望对你有一定的参考价值。
第二章 增强博客功能
在之前的章节创建基础的博客应用,现在可以变成具备通过邮件分享文章,带有评论和标签系统等功能的完整博客。在这一章会学习到如下内容:
使用Django发送邮件
创建表单并且通过视图控制表单
通过模型生成表单
集成第三方应用
更复杂的ORM查询
1通过邮件分享文章
首先来制作允许用户通过邮件分享文章链接的功能。在开始之前,先想一想你将如何使用视图、URLs和模板来实现这个功能,然后看一看你需要做哪些事情:
创建一个表单供用户填写名称和电子邮件地址收件人,以及评论等。
在views.py中创建一个视图控制这个表单,处理接收到的数据然后发送电子邮件
为新的视图在urls.py中配置URL
创建展示表单的模板
1.1使用Django创建表单
Django内置一个表单框架,可以简单快速的创建表单。表单框架具有自定义表单字段、确定实际显示的方式和验证数据的功能。
Django使用两个类创建表单:
Form:用于生成标准的表单
ModelForm:用于从模型生成表单
在blog应用中创建一个forms.py文件,然后编写:
Copyfrom django import forms
classEmailPostForm(forms.Form):
name = forms.CharField(max_length=25)
email = forms.EmailField()
to = forms.EmailField()
comments = forms.CharField(required=False, widget=forms.Textarea)
这是使用forms类创建的第一个标准表单,通过继承内置Form类,然后设置字段为各种类型,用于验证数据。
表单可以编写在项目的任何位置,但通常将其编写在对应应用的forms.py文件中。
name字段是Charfield类型,会被渲染为<input type="text">html标签。每个字段都有一个默认的widget参数决定该字段被渲染成的HTML元素类型,可以通过widget参数改写。在comments字段中,使用了widget=forms.Textarea令该字段被渲染为一个<textarea>元素,而不是默认的<input>元素。
字段验证也依赖于字段属性。例如:email和to字段都是EmailField类型,两个字段都接受一个有效的电子邮件格式的字符串,否则这两个字段会抛出forms.ValidationError错误。表单里还存在的验证是:name字段的最大长度maxlength是25个字符,comments字段的required=False表示该字段可以没有任何值。所有的这些设置都会影响到表单验证。本表单只使用了很少一部分的字段类型,关于所有表单字段可以参考https://docs.djangoproject.com/en/2.0/ref/forms/fields/。
1.2通过视图控制表单#
现在需要写一个视图,用于处理表单提交来的数据,当表单成功提交的时候发送电子邮件。编辑blog应用的views.py文件:
Copyfrom .forms import EmailPostForm
defpost_share(request, post_id):
# 通过id 获取 post 对象
post = get_object_or_404(Post, id=post_id, status='published')
if request.method == "POST":
# 表单被提交
form = EmailPostForm(request.POST)
if form.is_valid():
# 验证表单数据
cd = form.cleaned_data
# 发送邮件......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取得所有已经发布的文章中对应ID的文章。
这个视图同时用于显示空白表单和处理提交的表单数据。我们先通过request.method判断当前请求是POST还是GET请求。如果是GET请求,展示一个空白表单;如果是POST请求,需要处理表单数据。
处理表单数据的过程如下:
视图收到GET请求,通过form = EmailPostForm()创建一个空白的form对象,展示在页面中是一个空白的表单供用户填写。
用户填写并通过POST请求提交表单,视图使用request.POST中包含的表单数据创建一个表单对象:
Copyif request.method == 'POST':
# 表单被提交
form = EmailPostForm(request.POST)
在上一步之后,调用表单对象的is_valid()方法。这个方法会验证表单中所有的数据是否有效,如果全部通过验证会返回True,任意一个字段未通过验证,is_valid()就会返回False。如果返回False,此时可以在form.errors属性中查看错误信息。
如果表单验证失败,我们将这个表单对象渲染回页面,页面中会显示错误信息。
如果表单验证成功,可以通过form.cleaned_data属性访问表单内所有通过验证的数据,这个属性类似于一个字典,包含字段名与值构成的键值对。
如果表单验证失败,form.cleaned_data只会包含通过验证的数据。
现在就可以来学习如何使用Django发送邮件了。
1.3使用Django发送邮件
使用Django发送邮件比较简单,需要一个本地或者外部的SMTP服务器,然后在settings.py文件中加入如下设置:
EMAIL_HOST:邮件主机,默认是localhost
EMAIL_PORT:SMTP服务端口,默认是25
EMAIL_HOST_USER:SMTP服务器的用户名
EMAIL_HOST_PASSWORD:SMTP服务器的密码
EMAIL_USE_TLS:是否使用TLS进行连接
EMAIL_USE_SSL:是否使用SSL进行连接
如果无法使用任何SMTP服务器,则可以将邮件打印在命令行窗口中,在settings.py中加入下列这行:
CopyEMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
这样会把所有的邮件内容显示在控制台,非常便于测试。
如果没有本地SMTP服务器,可以使用很多邮件服务供应商提供的SMTP服务,以下是使用Google的邮件服务示例:
CopyEMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'your_account@gmail.com'
EMAIL_HOST_PASSWORD = 'your_password'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
输入python manage.py shell,在命令行环境中试验一下发送邮件的指令:
Copyfrom 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表示如果发送失败就抛出异常。如果看到返回1,就说明邮件成功发送。
如果采用以上设置无法成功使用Google的邮件服务,需要到https://myaccount.google.com/lesssecureapps,启用“允许不够安全的应用”,如下图所示:
现在我们把发送邮件的功能加入到视图中,编辑views.py中的post_share视图函数:
Copydefpost_share(request, post_id):
# 通过id 获取 post 对象
post = get_object_or_404(Post, id=post_id, status='published')
sent = Falseif request.method == "POST":
# 表单被提交
form = EmailPostForm(request.POST)
if form.is_valid():
# 表单字段通过验证
cd = form.cleaned_data
post_url = request.build_absolute_uri(post.get_absolute_url())
subject = ' () recommends you reading ""'.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, 'lee0709@vip.sina.com', [cd['to']])
sent = Trueelse:
form = EmailPostForm()
return render(request, 'blog/post/share.html', 'post': post, 'form': form, 'sent': sent)
声明了一个sent变量用于向模板返回邮件发送的状态,当邮件发送成功的时候设置为True。稍后将使用该变量显示一条成功发送邮件的消息。由于要在邮件中包含连接,因此使用了get_absolute_url()方法获取被分享文章的URL,然后将其作为request.build_absolute_uri()的参数转为完整的URL,再加上表单数据创建邮件正文,最后将邮件发送给to字段中的收件人。
还需要给视图配置URL,打开blog应用中的urls.py,加一条post_share的URL pattern:
Copyurlpatterns = [
# ...
path('<int:post_id>/share/', views.post_share, name='post_share'),
]
1.4在模板中渲染表单
在创建表单,视图和配置好URL之后,现在只剩下模板了。在blog/templates/blog/post/目录内创建share.html,添加如下代码:
Copy% extends "blog/base.html" %
% block title %Share a post% endblock %
% block content %
% if sent %
<h1>E-mail successfully sent</h1><p>
" post.title " was successfully sent to form.cleaned_data.to .
</p>
% else %
<h1>Share " post.title " by e-mail</h1><formaction="."method="post">
form.as_p
% csrf_token %
<inputtype="submit"value="Send e-mail"></form>
% endif %
% endblock %
这个模板在邮件发送成功的时候显示一条成功信息,否则则显示表单。你可能注意到了,创建了一个HTML表单元素并且指定其通过POST请求提交:
Copy<formaction="."method="post">
之后渲染表单实例,通过使用as_p方法,将表单中的所有元素以
元素的方式展现出来。还可以使用as_ul和as_table分别以列表和表格的形式显示。如果想分别渲染每个表单元素,可以迭代表单对象中的每个元素,例如这样:
Copy% for field in form %
<div>
field.errors
field.label_tag field
</div>
% endfor %
% csrf_token %在页面中显示为一个隐藏的input元素,是一个自动生成的防止跨站请求伪造(CSRF)攻击的token。跨站请求伪造是一种冒充用户在已经登录的Web网站上执行非用户本意操作的一种攻击方式,可能由其他网站或一段程序发起。关于CRSF的更多信息可以参考https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)。
例子生成的隐藏字段类似如下:
Copy<inputtype='hidden'name='csrfmiddlewaretoken'value='26JjKo2lcEtYkGoV9z4XmJIEHLXN5LDR' />
Django默认会对所有POST请求进行CSRF检查,在所有POST方式提交的表单中,都要添加csrf_token。
修改blog/post/detail.html将下列链接增加到 post.body|linebreaks 之后:
Copy<p><ahref="% url "blog:post_share" post.id %">Share this post</a></p>
这里的% url %标签,其功能和在视图中使用的reverse()方法类似,使用URL的命名空间blog和URL命名post_share,再传入一个ID作为参数,就可以构建出一个URL。在页面渲染时,% url %就会被渲染成反向解析出的URL。
现在使用python manage.py runserver启动站点,打开http://127.0.0.1:8000/blog/,点击任意文章查看详情页,在文章的正文下会出现分享链接,如下所示:
点击 Share this post 链接,可以看到分享页面:
这个表单的CSS样式表文件位于static/css/blog.css。当你点击SEND E-MAIL按钮的时候,就会提交表单并验证数据,如果有错误,可以看到页面如下:
在某些现代浏览器上,很有可能浏览器会阻止你提交表单,提示必须完成某些字段,这是因为浏览器在提交根据表单的HTML元素属性先进行了验证。现在通过邮件分享链接的功能制作完成了,下一步是创建一个评论系统。
关闭浏览器验证的方法是给表单添加novalidate属性:<form action="" novalidate>
2创建评论系统
现在,我们要创建一个评论系统,让用户可以对文章发表评论。创建评论系统需要进行以下步骤:
创建一个模型用于存储评论
创建一个表单用于提交评论和验证数据
创建一个视图用于处理表单和将表单数据存入数据库
编辑文章详情页以展示评论和提供增加新评论的表单
首先来创建评论对应的数据模型,编辑blog应用的models.py文件,添加以下代码:
CopyclassComment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
name = models.CharField(max_length=80)
email = models.EmailField()
body = models.TextField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)
classMeta:
ordering = ("created",)
def__str__(self):
return'Comment by on '.format(self.name, self.post)
Comment模型包含一个外键(ForeignKey)用于将评论与一个文章联系起来,定义了文章和评论的一对多关系,即一个文章下边可以有多个评论;外键的related_name参数定义了在通过文章查找其评论的时候引用该关联关系的名称。这样定义了该外键之后,可以通过comment.post获得一条评论对应的文章,通过post.comments.all()获得一个文章对应的所有评论。如果不定义related_name,Django会使用模型的小写名加上_set(comment_set)来作为反向查询的管理器名称。
关于一对多关系的可以参考https://docs.djangoproject.com/en/2.0/topics/db/examples/many_to_one/。
模型还包括一个active布尔类型字段,用于手工关闭不恰当的评论;还指定了排序方式为按照created字段进行排序。
新的Comment模型还没有与数据库同步,执行以下命令创建迁移文件:
Copypython manage.py makemigrations blog
会看到如下输出:
CopyMigrations for'blog':
blog/migrations/0002_comment.py
- Create model Comment
Django在migrations/目录下创建了0002_comment.py文件,现在可以执行实际的迁移命令将模型写入数据库:
Copypython manage.py migrate
会看到以下输出:
CopyApplying blog.0002_comment... OK
数据迁移的过程结束了,数据库中新创建了名为blog_comment的数据表。
译者注:数据迁移的部分在原书中重复次数太多,而且大部分无实际意义,如无需要特殊说明的地方,以下翻译将略过类似的部分,以“执行数据迁移”或类似含义的字样替代。
创建了模型之后,可以将其加入管理后台。打开blog应用中的admin.py文件,导入Comment模型,然后增加如下代码:
Copyfrom .models import Post, Comment
@admin.register(Comment)classCommentAdmin(admin.ModelAdmin):
list_display = ('name', 'email', 'post', 'created', 'active')
list_filter = ('active', 'created', 'updated')
search_fields = ('name', 'email', 'body')
启动站点,到http://127.0.0.1:8000/admin/查看管理站点,会看到新的模型已经被加入到管理后台中:
2.1根据模型创建表单
在发送邮件的功能里,采用继承forms.Form类的方式,自行编写各个字段创建了一个表单。Django对于表单有两个类:Form和ModelForm。这次我们使用ModelForm动态的根据Comment模型生成表单。编辑blog应用的forms.py文件:
Copyfrom .models import Comment
classCommentForm(forms.ModelForm):
classMeta:
model = Comment
fields = ('name', 'email', 'body')
依据模型创建表单,只需要在Meta类中指定基于哪个类即可。Django会自动内省该类然后创建对应的表单。我们对于模型字段的设置会影响到表单数据的验证规则。默认情况下,Django对每一个模型字段都创建一个对应的表单元素。然而,可以显示的通过Meta类中的fields属性指定需要创建表单元素的字段,或者使用exclude属性指定需要排除的字段。对于我们的CommentForm类,我们指定了表单只需要包含name,email和body字段即可。
2.2在视图中处理表单
由于提交评论的动作在文章详情页发生,所以把处理表单的功能整合进文章详情视图中会让代码更简洁。编辑views.py文件,导入Comment模型然后修改post_detail视图:
Copyfrom .models import Post, Comment
from .forms import EmailPostForm, CommentForm
defpost_detail(request, year, month, day, post):
post = get_object_or_404(Post, slug=post, status="published", publish__year=year, publish__month=month, publish__day=day)
# 列出文章对应的所有活动的评论
comments = post.comments.filter(active=True)
new_comment = Noneif request.method == "POST":
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
# 通过表单直接创建新数据对象,但是不要保存到数据库中
new_comment = comment_form.save(commit=False)
# 设置外键为当前文章
new_comment.post = post
# 将评论数据对象写入数据库
new_comment.save()
else:
comment_form = CommentForm()
return render(request, 'blog/post/detail.html',
'post': post, 'comments': comments, 'new_comment': new_comment, 'comment_form': comment_form)
现在post_detail视图可以显示文章及其评论,在视图中增加了一个获得当前文章对应的全部评论的QuerySet,如下:
Copycomments = post.comments.filter(active=True)
以Comments类中定义的外键的related_name属性的名称作为管理器,对post对象执行查询从而得到了所需的QuerySet。
同时还为这个视图增加了新增评论的功能。初始化了一个new_comment变量为None,用于标记一个新评论是否被创建。如果是GET请求,使用comment_form = CommentForm()创建空白表单;如果是POST请求,使用提交的数据生成表单对象并调用is_valid()方法进行验证。如果表单未通过验证,使用当前表单渲染页面以提供错误信息。如果表单通过验证,则进行如下工作:
调用当前表单的save()方法生成一个Comment实例并且赋给new_comment变量,就是下边这一行:
Copynew_comment = comment_form.save(commit=False)
Copy表单对象的`save()`方法会返回一个由当前数据构成的,表单关联的数据类的对象,并且会将这个对象写入数据库。如果指定`commit=False`,则数据对象会被创建但不会被写入数据库,便于在保存到数据库之前对对象进行一些操作。
将comment对象的外键关联指定为当前文章:new_comment.post = post,这样就明确了当前的评论是属于这篇文章的。
最后,调用save()方法将新的评论对象写入数据库:new_comment.save()
save()方法仅对ModelForm生效,因为Form类没有关联到任何数据模型。
2.3为文章详情页面添加评论
已经创建了用于管理一个文章的评论的视图,现在需要修改post/detail.html来做以下的事情:
展示当前文章的评论总数
列出所有评论
展示表单供用户添加新评论
首先要增加评论总数,编辑post/detail.html,将下列内容追加在content块内的底部:
Copy% with comments.count as total_comments %
<h2>
total_comments comment total_comments|pluralize
</h2>
% endwith %
我们在模板里使用了Django ORM,执行了comments.count()。在模板中执行一个对象的方法时,不需要加括号;也正因为如此,不能够执行必须带有参数的方法。% with %标签表示在% endwith %结束之前,都可以使用一个变量来代替另外一个变量或者值。
% with %标签经常用于避免反复对数据库进行查询和向模板传入过多变量。
这里使用了pluralize模板过滤器,用于根据total_comments的值显示复数词尾。将在下一章详细讨论模板过滤器。
如果值大于1,pluralize过滤器会返回一个带复数词尾"s"的字符串,实际渲染出的字符串会是0 comments,1 comment,2 comments或者N comments。
然后来增加评论列表的部分,在post/detail.html中上述代码之后继续追加:
Copy% for comment in comments %
<divclass="comment"><pclass="info">
Comment forloop.counter by comment.name
comment.created
</p>
comment.body|linebreaks
</div>
% empty %
<p>There are no comments yet.</p>
% endfor %
这里使用了% for %标签,用于循环所有的评论数据对象。如果comments对象为空,则显示一条信息提示用户没有评论。使用 forloop.counter 可以在循环中计数。然后,显示该条评论的发布者,发布时间和评论内容。
最后是显示表单或者一条成功信息的部分,在上述代码后继续追加:
Copy% if new_comment %
<h2>Your comment has been added.</h2>
% else %
<h2>Add a new comment</h2><formaction="."method="post">
comment_form.as_p
% csrf_token %
<p><inputtype="submit"value="Add comment"></p></form>
% endif %
这段代码的逻辑很直白:如果new_comment对象存在,显示一条成功信息,其他情况下则用as_p方法渲染整个表单以及CSRF token。在浏览器中打开http://127.0.0.1:8000/blog/,可以看到如下页面:
使用表单添加一些评论,然后刷新页面,应该可以看到评论以发布的时间排序:
在浏览器中打开http://127.0.0.1:8000/admin/blog/comment/,可以在管理后台中看到所有评论,点击其中的一个进行编辑,取消掉Active字段的勾,然后点击SAVE按钮。然后会跳回评论列表,Acitve栏会显示一个红色叉号表示该评论未被激活,如下图所示:
此时返回文章详情页,可以看到被设置为未激活的评论不会显示出来,也不会被统计到评论总数中。由于有了这个active字段,可以非常方便的控制评论显示与否而不需要实际删除。
3添加标签功能
在完成了评论系统之后,我们将来给文章加上标签系统。标签系统通过集成django-taggit第三方应用模块到我们的Django项目来实现,django-taggit提供一个Tag数据模型和一个管理器,可以方便的给任何模型加上标签。django-taggit的源代码位于:https://github.com/alex/django-taggit。
通过pip安装django-taggit:
Copypip install django_taggit==0.22.2
译者注:如果安装了django 2.1或更新版本,请下载最新版 django-taggit。原书的0.22.2版只能和Django 2.0.5版搭配使用。新版使用方法与0.22.2版没有任何区别。
之后在setting.py里的INSTALLED_APPS设置中增加taggit以激活该应用:
CopyINSTALLED_APPS = [
# ...'blog.apps.BlogConfig',
'taggit',
]
打开blog应用下的models.py文件,将django-taggit提供的TaggableMananger模型管理器加入到Post模型中:
Copyfrom taggit.managers import TaggableManager
classPost(models.Model):
# ......
tags=TaggableManager()
这个管理器可以对Post对象的标签进行增删改查。然后执行数据迁移。
现在数据库也已经同步完成了,先学习一下如何使用django-taggit模块和其tags管理器。使用python manage.py shell进入Python命令行然后输入下列命令:
之后来看如何使用,先到命令行里:
Copy>>> from blog.models import Post
>>> post = Post.objects.get(id=1)
然后给这个文章增加一些标签,然后再获取这些标签看一下是否添加成功:
Copy>>> post.tags.add('music', 'jazz', 'django')
>>> post.tags.all()
<QuerySet [<Tag: jazz>, <Tag: music>, <Tag: django>]>
删除一个标签再检查标签列表:
Copy>>> post.tags.remove('django')
>>> post.tags.all()
<QuerySet [<Tag: jazz>, <Tag: music>]>
操作很简单。启动站点然后到http://127.0.0.1:8000/admin/taggit/tag/,可以看到列出taggit应用中Tag对象的管理页面:
到http://127.0.0.1:8000/admin/blog/post/点击一篇文章进行修改,可以看到文章现在包含了一个标签字段,如下所示:
现在还需要在页面上展示标签,编辑blog/post/list.html,在显示文章的标题下边添加:
Copy<pclass="tags">Tags: post.tags.all|join:", " </p>
join过滤器的功能和Python字符串的join()方法很类似,打开http://127.0.0.1:8000/blog/,就可以看到在每个文章的标题下方列出了标签:
现在来编辑post_list视图,让用户可以根据一个标签列出具备该标签的所有文章,打开blog应用的views.py文件,从django-taggit中导入Tag模型,然后修改post_list视图,让其可以额外的通过标签来过滤文章:
Copyfrom taggit.models import Tag
defpost_list(request, tag_slug=None):
tag = Noneif tag_slug:
tag = get_object_or_404(Tag, slug=tag_slug)
object_list = object_list.filter(tags__in=[tag])
paginator = Paginator(object_list, 3) # 3 posts in each page# ......
post_list视图现在工作如下:
多接收一个tag_slug参数,默认值为None。这个参数将通过URL传入
在视图中,创建了初始的QuerySet用于获取所有的已发布的文章,然后判断如果传入了tag_slug,就通过get_object_or_404()方法获取对应的Tag对象
然后过滤初始的QuerySet,条件为文章的标签中包含选出的Tag对象,由于这是一个多对多关系,所以将Tag对象放入一个列表内选择。
QuerySet是惰性的,直到模板渲染过程中迭代posts对象列表的时候,QuerySet才被求值。
最后修改,视图底部的render()方法,把tag变量也传入模板。完整的视图如下:
Copydefpost_list(request, tag_slug=None):
object_list = Post.published.all()
tag = Noneif tag_slug:
tag = get_object_or_404(Tag, slug=tag_slug)
object_list = object_list.filter(tags__in=[tag])
paginator = Paginator(object_list, 3) # 3 posts in each page
page = request.GET.get('page')
try:
posts = paginator.page(page)
except PageNotAnInteger:
posts = paginator.page(1)
except EmptyPage:
posts = paginator.page(paginator.num_pages)
return render(request, 'blog/post/list.html', 'page': page, 'posts': posts, 'tag': tag)
打开blog应用的urls.py文件,注释掉PostListView那一行,取消post_list视图的注释,像下边这样:
Copypath('', views.post_list, name='post_list'),
# path('', views.PostListView.as_view(), name='post_list'),
再增加一行通过标签显示文章的URL:
Copypath('tag/<slug:tag_slug>/', views.post_list, name='post_list_by_tag'),
可以看到,两个URL指向了同一个视图,但命名不同。第一个URL不带任何参数去调用post_list视图,第二个URL则会带上tag_slug参数调用post_list视图。使用了一个<slug:tag_slug>获取参数。
由于我们将CBV改回为FBV,所以在blog/post/list.html里将include语句的变量改回FBV的posts:
Copy% include "pagination.html" with page=posts %
再增加显示文章标签的% for %循环的代码:
Copy% if tag %
<h2>Posts tagged with " tag.name "</h2>
% endif %
如果用户访问博客,可以看到全部的文章列表;如果用户点击某个具体标签,就可以看到具备该标签的文章。现在还需改变一下标签的显示方式:
Copy<pclass="tag">
Tags:
% for tag in post.tags.all %
<ahref="% url "blog:post_list_by_tag" tag.slug %"> tag.name </a>
% if not forloop.last %, % endif %
% endfor %
</p>
我们通过迭代所有标签,将标签设置为一个链接,指向通过该标签对应的所有文章。通过% url "blog:post_list_by_tag" tag.slug %反向解析出了链接。
现在到http://127.0.0.1:8000/blog/,然后点击任何标签,就可以看到该标签对应的文章列表:
4通过相似性获取文章
在为博客添加了标签功能之后,可以使用标签来做一些有趣的事情。一些相同主题的文章会具有相同的标签,可以创建一个功能给用户按照共同标签数量的多少推荐文章。
为了实现该功能,需要如下几步:
获得当前文章的所有标签
拿到所有具备这些标签的文章
把当前文章从这个文章列表里去掉以避免重复显示
按照具有相同标签的多少来排列
如果文章具有相同数量的标签,按照时间来排列
限制总推荐文章数目
这几个步骤会使用到更复杂的QuerySet,打开blog应用的views.py文件,在最上边增加一行:
Copyfrom django.db.models import Count
这是从Django ORM中导入的Count聚合函数,这个函数可以按照分组统计某个字段的数量,django.db.models还包含下列聚合函数:
Avg:计算平均值
Max:取最大值
Min:取最小值
Count:计数
Sum:求和
译者注:作者在此处有所保留,没有写Sum函数,此外还有Q查询
聚合查询的官方文档在https://docs.djangoproject.com/en/2.0/topics/db/aggregation/。
修改post_detail视图,在render()上边加上这一段内容,缩进与render()行同级:
Copydefpost_detail(request, year, month, day, post):
# ......# 显示相近Tag的文章列表
post_tags_ids = post.tags.values_list('id',flat=True)
similar_tags = Post.published.filter(tags__in=post_tags_ids).exclude(id=post.id)
similar_posts = similar_tags.annotate(same_tags=Count('tags')).order_by('-same_tags','-publish')[:4]
return render(......)
以上代码解释如下:
values_list方法返回指定的字段的值构成的元组,通过指定flat=True,让其结果变成一个列表比如[1, 2, 3, ...]
选出所有包含上述标签的文章并且排除当前文章
使用Count对每个文章按照标签计数,并生成一个新字段same_tags用于存放计数的结果
按照相同标签的数量,降序排列结果,然后截取前四个结果作为最终传入模板的数据对象。
最后修改render()函数将新生成的similar_posts传给模板:
Copyreturn render(request,
'blog/post/detail.html',
'post': post,
'comments': comments,
'new_comment': new_comment,
'comment_form': comment_form,
'similar_posts': similar_posts)
然后编辑blog/post/detail.html,将以下代码添加到评论列表之前:
Copy<h2>Similar posts</h2>
% for post in similar_posts %
<p><ahref=" post.get_absolute_url "> post.title </a></p>
% empty %
There are no similar posts yet.
% endfor %
现在的文章详情页面示例如下:
现在已经实现了该功能。django-taggit模块包含一个similar_objects()模型管理器也可以实现这个功能,可以在https://django-taggit.readthedocs.io/en/latest/api.html查看django-taggit的所有模型管理器使用方法。
还可以用同样的方法在文章详情页为文章添加标签显示。
总结
在这一章里了解了如何使用Django的表单和模型表单,为博客添加了通过邮件分享文章的功能和评论系统。第一次使用了Django的第三方应用,通过django-taggit为博客增添了基于标签的功能。最后进行复杂的聚合查询实现了通过标签相似性推荐文章。
下一章会学习如何创建自定义的模板标签和模板过滤器,创建站点地图和RSS feed,以及对博客文章实现全文检索功能。
如有不懂还要咨询下方小卡片,博主也希望和志同道合的测试人员一起学习进步
在适当的年龄,选择适当的岗位,尽量去发挥好自己的优势。
我的自动化测试开发之路,一路走来都离不每个阶段的计划,因为自己喜欢规划和总结,
测试开发视频教程、学习笔记领取传送门!!!
以上是关于增强博客功能的主要内容,如果未能解决你的问题,请参考以下文章