第二章 为博客添加高级功能

摘要

上一章中,你创建了一个基础的博客应用。现在,利用一些高级特性,你要把它打造成一个功能完整的博客,比如通过邮件分享帖子,添加评论,为帖子打上标签,以及通过相似度检索帖子。在这一章中,你会学习以下主题:

2 为博客添加高级功能

上一章中,你创建了一个基础的博客应用。现在,利用一些高级特性,你要把它打造成一个功能完整的博客,比如通过邮件分享帖子,添加评论,为帖子打上标签,以及通过相似度检索帖子。在这一章中,你会学习以下主题:

  • 使用Django发送邮件
  • 在视图中创建和处理表单
  • 通过模型创建表单
  • 集成第三方应用
  • 构造复杂的QuerySet

2.1 通过邮件分享帖子

首先,我们将会允许用户通过邮件分享帖子。花一点时间想想,通过上一章学到的知识,你会如何使用视图,URL和模板来完成这个功能。现在核对一下,允许用户通过邮件发送帖子需要完成哪些操作:

  • 为用户创建一个填写名字,邮箱,收件人和评论(可选的)的表单
  • views.py中创建一个视图,用于处理post数据和发送邮件
  • blog应用的urls.py文件中,为新视图添加URL模式
  • 创建一个显示表单的模板

2.1.1 使用Django创建表单

让我们从创建分享帖子的表单开始。Django有一个内置的表单框架,让你很容易的创建表单。表单框架允许你定义表单的字段,指定它们的显示方式,以及如何验证输入的数据。Django的表单框架还提供了一种灵活的方式,来渲染表单和处理数据。

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创建了一个表单。我们使用不同的字段类型,Django可以相应的验证字段。

表单可以放在Django项目的任何地方,但惯例是放在每个应用的forms.py文件中。

name字段是一个CharField。这种字段的类型渲染为<input type="text"> HTML元素。每种字段类型都有一个默认组件,决定了该字段如何在HTML中显示。可以使用widget属性覆盖默认组件。在comments字段中,我们使用Textarea组件显示为<textarea> HTML元素,而不是默认的<input>元素。

字段的验证也依赖于字段类型。例如,emailto字段是EmailField。这两个字段都要求一个有效的邮箱地址,否则字段验证会抛出forms.ValidationError异常,导致表单无效。表单验证时,还会考虑其它参数:我们定义name字段的最大长度为25个字符,并使用required=Falsecomments字段是可选的。字段验证时,这些所有因素都会考虑进去。这个表单中使用的字段类型只是Django表单字段的一部分。在这里查看所有可用的表单字段列表。

2.1.2 在视图中处理表单

你需要创建一个新视图,用于处理表单,以及提交成功后发送一封邮件。编辑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作为参数。
  • 我们通过ID,使用get_object_or_404()快捷方法检索状态为published的帖子。
  • 我们使用同一个视图=显示初始表单和处理提交的数据。根据request.method区分表单是否提交。我们将使用POST提交表单。如果我们获得一个GET请求,需要显示一个空的表单;如果获得一个POST请求,表单会被提交,并且需要处理它。因此,我们使用request.method == 'POST'来区分这两种场景。

以下是显示和处理表单的过程:

  1. 当使用GET请求初始加载视图时,我们创建了一个新的表单实例,用于在模板中显示空表单。

    form = EmailPostForm()

  2. 用户填写表单,并通过POST提交。接着,我们使用提交的数据创建一个表单实例,提交的数据包括在request.POST中:
    if request.POST == 'POST':   # Form was submitted   form = EmailPostForm(request.POST)
  3. 接着,我们使用表单的is_valid()方法验证提交的数据。该方法会验证表单中的数据,如果所有字段都是有效数据,则返回True。如果任何字段包含无效数据,则返回False。你可以访问form.errors查看验证错误列表。
  4. 如果表单无效,我们使用提交的数据在模板中再次渲染表单。我们将会在模板中显示验证错误。
  5. 如果表单有效,我们访问form.cleaned_data获得有效的数据。该属性是表单字段和值的字典。

如果你的表单数据无效,cleaned_data只会包括有效的字段。

现在,你需要学习如何使用Django发送邮件,把所有功能串起来。

2.1.3 使用Django发送邮件

使用Django发送邮件非常简单。首先,你需要一个本地SMTP服务,或者在项目的settings.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:是否使用隐式TLS加密连接。

如果你没有本地SMTP服务,可以使用你的邮箱提供商的SMTP服务。下面这个例子中的配置使用Google账户发送邮件:

EMAIL_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命令打开Python终端,如下发送邮件:

>>> 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,如果邮件不能正确发送,就会抛出异常。如果看到输出1,则表示邮件发送成功。如果你使用前面配置的Gmail发送邮件,你可能需要在这里启用低安全级别应用访问权限。

现在,我们把它添加到视图中。编辑blog应用中views.py文件的post_share视图,如下所示:

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             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, 'admin@blog.com', [cd['to']])             sent = True     else:         form = EmailPostForm()     return render(request,                     'blog/post/share.html',                     {'post': post, 'form': form, 'sent': sent})

注意,我们声明了一个sent变量,当帖子发送后,设置为True。当表单提交成功后,我们用该变量在模板中显示一条成功的消息。因为我们需要在邮件中包含帖子的链接,所以使用了get_absolute_url()方法检索帖子的绝对路径。我们把这个路径作为request.build_absolute_uri()的输入,构造一个包括HTTP模式(schema)和主机名的完整URL。我们使用验证后的表单数据构造邮件的主题和内容,最后发送邮件到表单to字段中的邮件地址。

现在,视图的开发工作已经完成,记得为它添加新的URL模式。打开blog应用的urls.py文件,添加post_share的URL模式:

urlpatterns = [     # ...     url(r'^(?P<post_id>/d+)/share/$', views.post_share, name='post_share'), ]

2.1.4 在模板中渲染表单

完成创建表单,编写视图和添加URL模式后,我们只缺少该视图的模板了。在blog/templates/blog/post/目录中创建share.html文件,添加以下代码:

{% 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 {{ cd.to }}.         </p>     {% else %}         <h1>Share "{{ post.title }}" by e-mail</h1>         <form action="." method="post">             {{ form.as_p }}             {% csrf_token %}             <input type="submit" value="Send e-mail">         </form>     {% endif %} {% endblock %}

这个模板用于显示表单,或者表单发送后的一条成功消息。正如你所看到的,我们创建了一个HTML表单元素,指定它需要使用POST方法提交:

<form action="." method="post">

然后,我们包括了实际的表单实例。我们告诉Django使用as_p方法,在HTML的<p>元素中渲染表单的字段。我们也可以使用as_ul把表单渲染为一个无序列表,或者使用as_table渲染为HTML表格。如果你想渲染每一个字段,我们可以这样迭代字段:

{% for field in form %}     <div>         {{ field.errors }}         {{ field.label_tag }} {{ field }}     </div> {% endfor %}

模板标签{% csrf_token %}使用自动生成的令牌引入一个隐藏字段,以避免跨站点请求伪造(CSRF)的攻击。这些攻击包含恶意网站或程序,对你网站上的用户执行恶意操作。你可以在这里找到更多相关的信息。

上述标签生成一个类似这样的隐藏字段:

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})

0

默认情况下,Django会检查所有POST请求中的CSRF令牌。记得在所有通过POST提交的表单中包括csrf_token标签。

编辑blog/post/detail.html模板,在{{ post.body|linebreaks }}变量之后添加链接,用于分享帖子的URL:

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})

1

记住,我们使用Django提供的{% url %}模板标签,动态生成URL。我们使用名为blog命名空间和名为post_share的URL,并传递帖子ID作为参数来构造绝对路径的URL。

现在,使用python manage.py runserver命令启动开发服务器,并在浏览器中打开http://127.0.0.1:8000/blog/。点击任何一篇帖子的标题,打开详情页面。在帖子正文下面,你会看到我们刚添加的链接,如下图所示:

第二章 为博客添加高级功能

点击Share this post,你会看到一个包含表单的页面,该页面可以通过邮件分享帖子。如下图所示:

第二章 为博客添加高级功能

该表单的CSS样式在static/css/blog.css文件中。当你点击Send e-mail按钮时,该表单会被提交和验证。如果所有字段都是有效数据,你会看到一条成功消息,如下图所示:

第二章 为博客添加高级功能

如果你输入了无效数据,会再次渲染表单,其中包括了所有验证错误:

第二章 为博客添加高级功能

译者注:不知道是因为浏览器不同,还是Django的版本不同,这里显示的验证错误跟原书中不一样。我用的是Chrome浏览器。

2.2 创建评论系统

现在,我们开始为博客构建评论系统,让用户可以评论帖子。要构建评论系统,你需要完成以下工作:

  • 创建一个保存评论的模型
  • 创建一个提交表单和验证输入数据的表单
  • 添加一个视图,处理表单和保存新评论到数据库中
  • 编辑帖子详情模板,显示评论列表和添加新评论的表单

首先,我们创建一个模型存储评论。打开blog应用的models.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})

2

这就是我们的Comment模型。它包含一个外键,把评论与单篇帖子关联在一起。这个多对一的关系在Comment模型中定义,因为每条评论对应一篇帖子,而每篇帖子可能有多条评论。从关联对象反向到该对象的关系由related_name属性命名。定义这个属性后,我们可以使用comment.post检索评论对象的帖子,使用post.comments.all()检索帖子的所有评论。如果你没有定义related_name属性,Django会使用模型名加_set(即comment_set)命名关联对象反向到该对象的管理器。

你可以在这里学习更多关于多对一的关系。

我们使用了active布尔字段,用于手动禁用不合适的评论。我们使用created字段排序评论,默认按时间排序。

刚创建的Comment模型还没有同步到数据库。运行以下命令,生成一个新的数据库迁移,反射创建的新模型:

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})

3

你会看到以下输出:

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})

4

Django在blog应用的migrations/目录中生成了0002_comment.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})

5

你会得到一个包括下面这一行的输出:

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})

6

我们刚创建的数据库迁移已经生效,数据库中已经存在一张新的blog_comment表。

现在我们可以添加新的模型到管理站点,以便通过简单的界面管理评论。打开blog应用的admin.py文件,导入Comment模型,并增加CommentAdmin类:

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})

7

使用python manage.py runserver命令启动开发服务器,并在浏览器中打开http://127.0.0.1:8000/admin/。你会在Blog中看到新的模型,如下图所示:

第二章 为博客添加高级功能

我们的模型已经在管理站点注册,并且可以使用简单的界面管理Comment实例。

2.2.1 通过模型创建表单

我们仍然需要创建一个表单,让用户可以评论博客的帖子。记住,Django有两个基础类用来创建表单:FormModelForm。之前你使用了第一个,让用户可以通过邮件分享帖子。在这里,你需要使用ModelForm,因为你需要从Comment模型中动态的创建表单。编辑blog应用的forms.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})

8

要通过模型创建表单,我们只需要在表单的Meta类中指定,使用哪个模型构造表单。Django自省模型,并动态的为我们创建表单。每种模型字段类型都有相应的默认表单字段类型。我们定义模型字段的方式考虑了表单的验证。默认情况下,Django为模型中的每个字段创建一个表单字段。但是,你可以使用fields列表明确告诉框架,你想在表单中包含哪些字段,或者使用exclude列表定义你想排除哪些字段。对应CommentForm,我们只使用nameemail,和body字段,因为用户只可能填写这些字段。

2.2.2 在视图中处理ModelForm

为了简单,我们将会使用帖子详情页面实例化表单,并处理它。编辑views.py文件,导入Comment模型和CommentForm表单,并修改post_detail视图,如下所示:

译者注:原书中是编辑models.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})

9

让我们回顾一下,我们往视图里添加了什么。我们使用post_detail视图显示帖子和它的评论。我们添加了一个QuerySet,用于检索该帖子所有有效的评论:

if request.POST == 'POST':   # Form was submitted   form = EmailPostForm(request.POST)

0

我们从post对象开始创建这个QuerySet。我们在Comment模型中使用related_name属性,定义了关联对象的管理器为comments。这里使用了这个管理器。

同时,我们使用同一个视图让用户添加新评论。因此,如果视图通过GET调用,我们使用comment_form = CommentForm()创建一个表单实例。如果是POST请求,我们使用提交的数据实例化表单,并使用is_valid()方法验证。如果表单无效,我们渲染带有验证错误的模板。如果表单有效,我们完成以下操作:

  1. 通过调用表单的save()方法,我们创建一个新的Comment对象:

    new_comment = comment_form.save(commit=False)

    save()方法创建了一个链接到表单模型的实例,并把它存到数据库中。如果使用commit=False调用,则只会创建模型实例,而不会存到数据库中。当你想在存储之前修改对象的时候,会非常方便,之后我们就是这么做的。save()只对ModelForm实例有效,对Form实例无效,因为它们没有链接到任何模型。

  2. 我们把当前的帖子赋值给刚创建的评论:

    new_comment.post = post

    通过这个步骤,我们指定新评论属于给定的帖子。

  3. 最后,使用下面的代码,把新评论存到数据库中:

    new_comment.save()

现在,我们的视图已经准备好了,可以显示和处理新评论了。

2.2.3 在帖子详情模板中添加评论

我们已经为帖子创建了管理评论的功能。现在我们需要修改blog/post/detail.html模板,完成以下工作:

  • 为帖子显示评论总数
  • 显示评论列表
  • 显示一个表单,用户增加评论

首先,我们会添加总评论数。打开detail.html模板,在content块中添加以下代码:

if request.POST == 'POST':   # Form was submitted   form = EmailPostForm(request.POST)

1

我们在模板中使用Django ORM执行comments.count()这个QuerySet。注意,Django模板语言调用方法时不带括号。{% with %}标签允许我们把值赋给一个变量,我们可以在{% endwith %}标签之前一直使用它。

{% with %}模板标签非常有用,它可以避免直接操作数据库,或者多次调用昂贵的方法。

我们使用了pluralize模板过滤器,根据total_comments的值决定是否显示单词comment的复数形式。模板过滤器把它们起作用变量的值作为输入,并返回一个计算后的值。我们会在第三章讨论模板过滤器。

如果值不是1,pluralize模板过滤器会显示一个“s”。上面的文本会渲染为0 comments1 comment,或者N comments。Django包括大量的模板标签和过滤器,可以帮助你以希望的方式显示信息。

现在,让我们添加评论列表。在上面代码后面添加以下代码:

if request.POST == 'POST':   # Form was submitted   form = EmailPostForm(request.POST)

2

我们使用{% for %}模板标签循环所有评论。如果comments列表为空,显示一个默认消息,告诉用户该帖子还没有评论。我们使用{{ forloop.counter }}变量枚举评论,它包括每次迭代中循环的次数。然后我们显示提交评论的用户名,日期和评论的内容。

最后,当表单成功提交后,我们需要渲染表单,或者显示一条成功消息。在上面的代码之后添加以下代码:

if request.POST == 'POST':   # Form was submitted   form = EmailPostForm(request.POST)

3

代码非常简单:如果new_comment对象存在,则显示一条成功消息,因为已经创建评论成功。否则渲染表单,每个字段使用一个<p>元素,以及POST请求必需的CSRF令牌。在浏览器中打开http://127.0.0.1:8000/blog/,点击一条帖子标题,打开详情页面,如下图所示:

第二章 为博客添加高级功能

使用表单添加两条评论,它们会按时间顺序显示在帖子下方,如下图所示:

第二章 为博客添加高级功能

在浏览器中打开http://127.0.0.1:8000/admin/blog/comment/,你会看到带有刚创建的评论列表的管理页面。点击某一条编辑,不选中Active选择框,然后点击Save按钮。你会再次被重定向到评论列表,该评论的Active列会显示一个禁用图标。类似下图的第一条评论:

第二章 为博客添加高级功能

如果你回到帖子详情页面,会发现被删除的评论没有显示;同时也没有算在评论总数中。多亏了active字段,你可以禁用不合适的评论,避免它们在帖子中显示。

2.3 增加标签功能

实现评论系统之后,我们准备为帖子添加标签。我们通过在项目中集成一个第三方的Django标签应用,来实现这个功能。django-taggit是一个可复用的应用,主要提供了一个Tag模型和一个管理器,可以很容易的为任何模型添加标签。你可以在这里查看它的源码。

首先,你需要通过pip安装django-taggit,运行以下命令:

if request.POST == 'POST':   # Form was submitted   form = EmailPostForm(request.POST)

4

然后打开mysite项目的settings.py文件,添加taggitINSTALLED_APPS设置中:

if request.POST == 'POST':   # Form was submitted   form = EmailPostForm(request.POST)

5

打开blog应用的models.py文件,添加django-taggit提供的TaggableManager管理器到Post模型:

if request.POST == 'POST':   # Form was submitted   form = EmailPostForm(request.POST)

6

tags管理器允许你从Post对象中添加,检索和移除标签。

运行以下命令,为模型改变创建一个数据库迁移:

if request.POST == 'POST':   # Form was submitted   form = EmailPostForm(request.POST)

7

你会看下以下输出:

if request.POST == 'POST':   # Form was submitted   form = EmailPostForm(request.POST)

8

现在,运行以下命令创建django-taggit模型需要的数据库表,并同步模型的变化:

if request.POST == 'POST':   # Form was submitted   form = EmailPostForm(request.POST)

9

你会看到迁移数据库生效的输入,如下所示:

EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST_USER = 'your_account@gmail.com' EMAIL_HOST_PASSWORD = 'your_password' EMAIL_PORT = 587 EMAIL_USE_TLS = True

0

你的数据库已经为使用django-taggit模型做好准备了。使用python manage.py shell打开终端,学习如何使用tags管理器。

首先,我检索其中一个帖子(ID为3的帖子):

EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST_USER = 'your_account@gmail.com' EMAIL_HOST_PASSWORD = 'your_password' EMAIL_PORT = 587 EMAIL_USE_TLS = True

1

接着给它添加标签,并检索它的标签,检查是否添加成功:

EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST_USER = 'your_account@gmail.com' EMAIL_HOST_PASSWORD = 'your_password' EMAIL_PORT = 587 EMAIL_USE_TLS = True

2

最后,移除一个标签,并再次检查标签列表:

EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST_USER = 'your_account@gmail.com' EMAIL_HOST_PASSWORD = 'your_password' EMAIL_PORT = 587 EMAIL_USE_TLS = True

3

这很容易,对吧?运行python manage.py runserver,再次启动开发服务器,并在浏览器中打开http://127.0.0.1:8000/admin/taggit/tag/。你会看到taggit应用管理站点,其中包括Tag对象的列表:

第二章 为博客添加高级功能

导航到http://127.0.0.1:8000/admin/blog/post/,点击一条帖子编辑。你会看到,现在帖子包括一个新的Tags字段,如下图所示,你可以很方便的编辑标签:

第二章 为博客添加高级功能

现在,我们将会编辑博客帖子,来显示标签。打开blog/post/list.html模板,在帖子标题下面添加以下代码:

EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST_USER = 'your_account@gmail.com' EMAIL_HOST_PASSWORD = 'your_password' EMAIL_PORT = 587 EMAIL_USE_TLS = True

4

模板过滤器join与Python字符串的join()方法类似,用指定的字符串连接元素。在浏览器中打开http://127.0.0.1:8000/blog/。你会看到每篇帖子标题下方有标签列表:

第二章 为博客添加高级功能

现在,我们将要编辑post_list视图,为用户列出具有指定标签的所有帖子。打开blog应用的views.py文件,从django-taggit导入Tag模型,并修改post_list视图,可选的通过标签过滤帖子:

EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST_USER = 'your_account@gmail.com' EMAIL_HOST_PASSWORD = 'your_password' EMAIL_PORT = 587 EMAIL_USE_TLS = True

5

该视图是这样工作的:

  1. 该视图接收一个默认值为None的可选参数tag_slug。该参数会在URL中。
  2. 在视图中,我们创建了初始的QuerySet,检索所有已发布的帖子,如果给定了标签别名,我们使用get_object_or_404()快捷方法获得给定别名的Tag对象。
  3. 然后,我们过滤包括给定标签的帖子列表。因为这是一个多对多的关系,所以我们需要把过滤的标签放在指定列表中,在这个例子中只包含一个元素。

记住,QeurySet是懒惰的。这个QuerySet只有在渲染模板时,循环帖子列表时才会计算。

最后,修改视图底部的render()函数,传递tag变量到模板中。视图最终是这样的:

EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST_USER = 'your_account@gmail.com' EMAIL_HOST_PASSWORD = 'your_password' EMAIL_PORT = 587 EMAIL_USE_TLS = True

6

打开blog应用的urls.py文件,注释掉基于类PostListView的URL模式,取消post_list视图的注释:

EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST_USER = 'your_account@gmail.com' EMAIL_HOST_PASSWORD = 'your_password' EMAIL_PORT = 587 EMAIL_USE_TLS = True

7

添加以下URL模式,通过标签列出帖子:

EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST_USER = 'your_account@gmail.com' EMAIL_HOST_PASSWORD = 'your_password' EMAIL_PORT = 587 EMAIL_USE_TLS = True

8

正如你所看到的,两个模式指向同一个视图,但是名称不一样。第一个模式不带任何可选参数调用post_list视图,第二个模式使用tag_slug参数调用视图。

因为我们使用的是post_list视图,所以需要编辑blog/post/list.hmlt模板,修改pagination使用posts参数:

EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST_USER = 'your_account@gmail.com' EMAIL_HOST_PASSWORD = 'your_password' EMAIL_PORT = 587 EMAIL_USE_TLS = True

9

{% for %}循环上面添加以下代码:

>>> 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)

0

如果用户正在访问博客,他会看到所有帖子列表。如果他通过指定标签过滤帖子,就会看到这个信息。现在,修改标签的显示方式:

>>> 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)

1

现在,我们循环一篇帖子的所有标签,显示一个自定义链接到URL,以便使用该便签过滤帖子。我们用{% url "blog:post_list_by_tag" tag.slug %}构造URL,把URL名和标签的别名作为参数。我们用逗号分隔标签。

在浏览器中打开http://127.0.0.1:8000/blog/,点击某一个标签链接。你会看到由该标签过滤的帖子列表:

第二章 为博客添加高级功能

2.4 通过相似度检索帖子

现在,我们已经为博客帖子添加了标签,我们还可以用标签做更多有趣的事。通过便签,我们可以很好的把帖子分类。主题类似的帖子会有几个共同的标签。我们准备增加一个功能:通过帖子共享的标签数量来显示类似的帖子。在这种情况下,当用户阅读一篇帖子的时候,我们可以建议他阅读其它相关帖子。

为某个帖子检索相似的帖子,我们需要:

  • 检索当前帖子的所有标签。
  • 获得所有带这些便签中任何一个的帖子。
  • 从列表中排除当前帖子,避免推荐同一篇帖子。
  • 通过和当前帖子共享的标签数量来排序结果。
  • 如果两篇或以上的帖子有相同的标签数量,推荐最近发布的帖子。
  • 限制我们想要推荐的帖子数量。

这些步骤转换为一个复杂的QuerySet,我们需要在post_detail视图中包含它。打开blog应用的views.py文件,在顶部添加以下导入:

>>> 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)

2

这是Django ORM的Count汇总函数。此函数允许我们执行汇总计数。然后在post_detail视图的render()函数之前添加以下代码:

>>> 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)

3

这段代码完成以下操作:

  1. 我们获得一个包含当前帖子所有标签的ID列表。values_list()这个QuerySet返回指定字段值的元组。我们传递flat=True给它,获得一个[1, 2, 3, ...]的列表。
  2. 我们获得包含这些标签中任何一个的所有帖子,除了当前帖子本身。
  3. 我们使用Count汇总函数生成一个计算后的字段same_tags,它包含与所有查询标签共享的标签数量。
  4. 我们通过共享的标签数量排序结果(降序),共享的标签数量相等时,用publish优先显示最近发布的帖子。我们对结果进行切片,只获取前四篇帖子。

render()函数添加similar_posts对象到上下文字典中:

>>> 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)

4

现在,编辑blog/post/detail.html模板,在帖子的评论列表前添加以下代码:

>>> 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)

5

推荐你在帖子详情模板中也添加标签列表,就跟我们在帖子列表模板中所做的那样。现在,你的帖子详情页面应该看起来是这样的:

第二章 为博客添加高级功能

译者注:需要给其它帖子添加标签,才能看到上图所示的相似的帖子。

你已经成功的推荐了相似的帖子给用户。django-taggit也包含一个similar_objects()管理器,可以用来检索共享的标签。你可以在这里查看所有django-taggit管理器。

2.5 总结

在这一章中,你学习了如何使用Django表单和模型表单。你创建了一个可以通过邮件分享网站内容的系统,还为博客创建了评论系统。你为帖子添加了标签,集成了一个可复用的应用,并创建了一个复杂的QuerySet,通过相似度检索对象。

下一章中,你会学习如何创建自定义模板标签和过滤器。你还会构建一个自定义的站点地图和帖子的RSS源,并在应用中集成一个高级的搜索引擎。

免责声明:本文来自于网络,如有侵权,请联系本站管理员,将立即删除侵权内容!

weinxin
我的微信
有问题微信找我
DannyWu

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

Protected with IP Blacklist CloudIP Blacklist Cloud