Django搭建blog网站(二)

10、页面侧边栏:使用自定义模板标签

我们的博客侧边栏有四项内容:最新文章、归档、分类和标签云。这些内容相对比较固定,且在各个页面都会显示,如果像文章列表或者文章详情一样,从视图函数中获取然后传递给模板,则每个页面对应的视图函数里都要写一段获取这些内容的代码,这会导致很多重复代码。更好的解决方案是直接在模板中获取,为此,我们使用 Django 的一个新技术:自定义模板标签来完成任务。

使用模板标签的思路

我们前面已经接触过一些 Django 内置的模板标签,比如比较简单的 {% static %} 模板标签,这个标签帮助我们在模板中引入静态文件。还有比较复杂的如 {% for %} {% endfor%} 标签。这里 我们希望自己定义一个模板标签,例如名为 get_recent_posts 的模板标签,它可以这样工作:我们只要在模板中写入 {% get_recent_posts as recent_post_list %},那么模板中就会有一个从数据库获取的最新文章列表,并通过 as 语句保存到 recent_post_list 模板变量里。这样我们就可以通过 {% for %} {% endfor%} 模板标签来循环这个变量,显示最新文章列表了,这和我们在编写博客首页面视图函数是类似的。首页视图函数中从数据库获取文章列表并保存到 post_list 变量,然后把这个 post_list 变量传给模板,模板使用 for 模板标签循环这个文章列表变量,从而展示一篇篇文章。这里唯一的不同是我们从数据库获取文章列表的操作不是在视图函数中进行,而是在模板中通过自定义的 {% get_recent_posts %} 模板标签进行。

以上就是解决思路,但模板标签不是我们随意写的,必须遵循 Django 的规范我们才能在 Django 的模板系统中使用自定义的模板标签,下面我们就依照这些规范来实现我们的需求。

模板标签目录结构

首先在我们的 blog 应用下创建一个 templatetags 文件夹。然后在这个文件夹下创建一个 __init__.py 文件,使这个文件夹成为一个 Python 包,之后在 templatetags\ 目录下创建一个 blog_tags.py 文件,这个文件存放自定义的模板标签代码。

接下来就是编写各个模板标签的代码了,自定义模板标签代码写在 blog_tags.py 文件中。其实模板标签本质上就是一个 Python 函数,因此按照 Python 函数的思路来编写模板标签的代码就可以了

10.1.最新文章模板标签

打开 blog_tags.py 文件,开始写最新文章模板标签。

blog/templatetags/blog_tags.py

from ..models import Post

def get_recent_posts(num=5):
    return Post.objects.all().order_by('-created_time')[:num]

这个函数的功能是获取数据库中前 num 篇文章,这里 num 默认为 5。函数就这么简单,但目前它还只是一个纯 Python 函数,Django 在模板中还不知道该如何使用它。为了能够通过 {% get_recent_posts %} 的语法在模板中调用这个函数,必须按照 Django 的规定注册这个函数为模板标签,方法如下:

blog/templatetags/blog_tags.py

from django import template
from ..models import Post

register = template.Library()

@register.simple_tag
def get_recent_posts(num=5):
    return Post.objects.all().order_by('-created_time')[:num]

这里首先导入 template 这个模块,然后实例化了一个 template.Library 类,并将函数 get_recent_posts 装饰为 register.simple_tag。这样就可以在模板中使用语法 {% get_recent_posts %} 调用这个函数了。

10.2.归档模板标签

和最新文章模板标签一样,先写好函数,然后将函数注册为模板标签即可。

blog/templatetags/blog_tags.py

@register.simple_tag
def archives():
    return Post.objects.dates('created_time', 'month', order='DESC')

这里 dates 方法会返回一个列表,列表中的元素为每一篇文章(Post)的创建时间,且是 Python 的 date 对象,精确到月份,降序排列。接受的三个参数值表明了这些含义,一个是 created_time ,即 Post 的创建时间,month 是精度,order='DESC' 表明降序排列(即离当前越近的时间越排在前面)。例如我们写了 3 篇文章,分别发布于 2018 年 2 月 14 日、2018 年 3 月 14 日、2018 年 3 月 15 日,那么 dates 函数将返回 2018 年 3 月 和 2018 年 2 月这样一个时间列表,且降序排列,从而帮助我们实现按月归档的目的。

10.3.分类标签模板

过程还是一样,先写好函数,然后将函数注册为模板标签。先导入Categor类

blog/templatetags/blog_tags.py

from ..models import Post, Category

@register.simple_tag
def get_categories():
    # 别忘了在顶部引入 Category 类
    return Category.objects.all()

尽管侧边栏有 4 项内容(还有一个标签云),但是这里我们只实现最新文章、归档和分类数据的显示,还有一个标签云没有实现。因为标签云的实现稍有一点不同

10.4.使用自定的模板标签

打开 base.html,为了使用模板标签,我们首先需要在模板中导入存放这些模板标签的模块,这里是 blog_tags.py 模块。当时我们为了使用 static 模板标签时曾经导入过 {% load staticfiles %},这次在 {% load staticfiles %} 下再导入 blog_tags:

templates/base.html

{% load staticfiles %}
{% load blog_tags %}
<!DOCTYPE html>
<html>
...
</html>

然后找到最新文章列表处,把里面的列表修改一下:

templates/base.html

<div class="widget widget-recent-posts">
  <h3 class="widget-title">最新文章</h3>
  {% get_recent_posts as recent_post_list %}
  <ul>
    {% for post in recent_post_list %}
    <li>
      <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
    </li>
    {% empty %}
    暂无文章!
    {% endfor %}
  </ul>
</div>

这里我们通过使用 get_recent_posts 模板标签获取到最新文章列表,然后我们通过 as 语法(Django 模板系统的语法)将获取的文章列表保存进了 recent_post_list 模板变量中,之后就可以通过 for 循环来循环显示文章列表数据了,这和我们在写首页视图时是一样的。

然后是归档部分:

templates/base.html

<div class="widget widget-archives">
  <h3 class="widget-title">归档</h3>
  {% archives as date_list %}
  <ul>
    {% for date in date_list %}
    <li>
      <a href="#">{{ date.year }} 年 {{ date.month }} 月</a>
    </li>
    {% empty %}
    暂无归档!
    {% endfor %}
  </ul>
</div>

同样,这里我们调用 archives 模板标签自动获取一个已发表文章的日期列表,精确到月份,降序排列,然后通过 as 语法将其保存在 date_list 模板变量里。由于日期列表中的元素为 Python 的 date 对象,因此可以通过其 year 和 month 属性分别获取年和月的信息,<a href="#">{{ date.year }} 年 {{ date.month }} 月</a> 反应了这个事实。

分类部分也一样:

<div class="widget widget-category">
  <h3 class="widget-title">分类</h3>
  {% get_categories as category_list %}
  <ul>
    {% for category in category_list %}
    <li>
      <a href="#">{{ category.name }} <span class="post-count">(13)</span></a>
    </li>
    {% empty %}
    暂无分类!
    {% endfor %}
  </ul>
</div>

<span class="post-count">(13)</span> 显示的是该分类下的文章数目,这个特性会在接下来的教程中讲解如何实现,目前暂时用占位数据代替吧。

现在运行开发服务器,可以看到侧边栏显示的数据已经不再是之前的占位数据,而是我们保存在数据库中的数据了。

 十一、分类与归档

 侧边栏已经正确地显示了最新文章列表、归档、分类等信息。现在来完善归档和分类功能,当用户点击归档下的某个日期或者分类下的某个分类时,跳转到文章列表页面,显示该日期或者分类下的全部文章。

 11.1.归档页面

要显示某个归档日期下的文章列表,思路和显示主页文章列表是一样的,回顾一下主页视图的代码:

blog/views.py

def index(request):
    post_list = Post.objects.all().order_by('-created_time')
    return render(request, 'blog/index.html', {'post_list': post_list})

主页视图函数中我们通过 Post.objects.all() 获取全部文章,而在我们的归档和分类视图中,我们不再使用 all 方法获取全部文章,而是使用 filter 来根据条件过滤。先来看归档视图:

blog/views.py

def archives(request, year, month):
    post_list = Post.objects.filter(created_time__year=year,
                                    created_time__month=month
                                    ).order_by('-created_time')
    return render(request, 'blog/index.html', {'post_list': post_list})

这里我们使用了模型管理器(objects)的 filter 函数来过滤文章。由于是按照日期归档,因此这里根据文章发表的年和月来过滤。具体来说,就是根据 created_time 的 year 和 month 属性过滤,筛选出文章发表在对应的 year 年和 month 月的文章。注意这里 created_time 是 Python 的 date 对象,其有一个 year 和 month 属性,我们在 页面侧边栏:使用自定义模板标签 使用过这个属性。Python 中类实例调用属性的方法通常是 created_time.year,但是由于这里作为函数的参数列表,所以 Django 要求我们把点替换成了两个下划线,即 created_time__year。同时和 index 视图中一样,我们对返回的文章列表进行了排序。此外由于归档的下的文章列表的显示和首页是一样的,因此我们直接渲染了index.html 模板。

 写好视图函数后就是配置好 URL:

blog/urls.py

from django.conf.urls import url

from . import views

app_name = 'blog'
urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^post/(?P<pk>[0-9]+)/$', views.detail, name='detail'),
    url(r'^archives/(?P<year>[0-9]{4})/(?P<month>[0-9]{1,2})/$', views.archives, name='archives'),
]

这个归档视图对应的 URL 的正则表达式和 detail 视图函数对应的 URL 是类似的,这在之前我们讲过。两个括号括起来的地方是两个命名组参数,Django 会从用户访问的 URL 中自动提取这两个参数的值,然后传递给其对应的视图函数。例如如果用户想查看 2018 年 3 月下的全部文章,他访问 /archives/2018/3/,那么 archives 视图函数的实际调用为:archives(request, year=2018, month=3)

在模板找到归档列表部分的代码,修改超链接的 href 属性,让用户点击超链接后跳转到文章归档页面:

templates/base.html

{% for date in date_list %}
<li>
  <a href="{% url 'blog:archives' date.year date.month %}">
    {{ date.year }} 年 {{ date.month }} 月
  </a>
</li>
{% endfor %}

这里 {% url %} 这个模板标签的作用是解析视图函数 blog:archives 对应的 URL 模式,并把 URL 模式中的年和月替换成 date.yeardate.month 的值。例如 blog:archives 表示 blog 应用下的 archives 函数,这个函数对应的 URL 模式为 ^archives/(?P<year>[0-9]{4})/(?P<month>[0-9]{1,2})/$,假设 date.year=2018date.month=5,那么 {% url 'blog:archives' date.year date.month %} 模板标签返回的值为/archives/2018/5/。

为什么要使用 {% url %} 模板标签呢?事实上,我们把超链接的 href 属性设置为 /archives/{{ date.year }}/{{ date.month }}/ 同样可以达到目的,但是这种写法是硬编码的。虽然现在 blog:archives 视图函数对应的 URL 模式是这种形式,但是如果哪天这个模式改变了呢?如果使用了硬编码的写法,那你需要把每一处 /archives/{{ date.year }}/{{ date.month }}/ 修改为新的模式。但如果使用了 {% url %} 模板标签,则不用做任何修改。

 测试一下,点击侧边栏归档的日期,跳转到归档页面,发现并没有显示归档下的文章列表,因为还要改一下时区:

首先安装pytz模块(django处理时区用的,安装即可,无需其它操作),然后更改settings设置

#settings.py

LANGUAGE_CODE = 'zh-hans'

TIME_ZONE = 'Asia/Shanghai'

USE_I18N = True

USE_L10N = True

USE_TZ = False

再次测试,发现可以显示归档下的文章列表了。

 11.2.分类页面

同样的写好分类页面的视图函数:

blog/views.py

import markdown

from django.shortcuts import render, get_object_or_404

# 引入 Category 类
from .models import Post, Category

def category(request, pk):
    # 记得在开始部分导入 Category 类
    cate = get_object_or_404(Category, pk=pk)
    post_list = Post.objects.filter(category=cate).order_by('-created_time')
    return render(request, 'blog/index.html', context={'post_list': post_list})

这里我们首先根据传入的 pk 值(也就是被访问的分类的 id 值)从数据库中获取到这个分类。get_object_or_404 函数和 detail 视图中一样,其作用是如果用户访问的分类不存在,则返回一个 404 错误页面以提示用户访问的资源不存在。然后我们通过 filter 函数过滤出了该分类下的全部文章。同样也和首页视图中一样对返回的文章列表进行了排序。

URL 配置如下:

blog/urls.py

from django.conf.urls import url

from . import views

app_name = 'blog'
urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^post/(?P<pk>[0-9]+)/$', views.detail, name='detail'),
    url(r'^archives/(?P<year>[0-9]{4})/(?P<month>[0-9]{1,2})/$', views.archives, name='archives'),
    url(r'^category/(?P<pk>[0-9]+)/$', views.category, name='category'),
]

修改相应模板:

templates/base.html

{% for category in category_list %}
<li>
  <a href="{% url 'blog:category' category.pk %}">{{ category.name }}</a>
</li>
{% endfor %}

同样,{% url %} 模板标签的用法和写归档页面时的用法是一样的。现在尝试点击相应的链接,就可以跳转到归档或者分类页面了。

 十二、评论

 相对来说,评论其实是另外一个比较独立的功能。Django 提倡,如果功能相对比较独立的话,最好是创建一个应用,把相应的功能代码写到这个应用里。我们的第一个应用叫 blog,它里面放了展示博客文章列表和细节等相关功能的代码。而这里我们再创建一个应用,名为 comments,这里面将存放和评论功能相关的代码。

python manage.py startapp comments

创建新的应用后一定要记得在 settings.py 里注册这个应用,Django 才知道这是一个应用

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',
    'comments'
]

12.1.设计评论的数据库模型

 用户评论的数据必须被存储到数据库里,以便其他用户访问时 Django 能从数据库取回这些数据然后展示给访问的用户,因此我们需要为评论设计数据库模型,这和设计文章、分类、标签的数据库模型是一样的,评论模型设计如下(评论模型的代码写在 comment\models.py 里):

comments/models.py

from django.db import models
class Comment(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField(max_length=255)
    url = models.URLField(blank=True)
    text = models.TextField()
    created_time = models.DateTimeField(auto_now_add=True)

    post = models.ForeignKey('blog.Post')

    def __str__(self):
        return self.text[:20]

这里我们会保存评论用户的 name(名字)、email(邮箱)、url(个人网站),用户发表的内容将存放在 text 字段里,created_time 记录评论时间。最后,这个评论是关联到某篇文章(Post)的,由于一个评论只能属于一篇文章,一篇文章可以有多个评论,是一对多的关系,因此这里我们使用了 ForeignKey。关于 ForeKey 我们前面已有介绍,这里不再赘述。

同时注意我们为 DateTimeField 传递了一个 auto_now_add=True 的参数值。auto_now_add 的作用是,当评论数据保存到数据库时,自动把 created_time 的值指定为当前时间。created_time 记录用户发表评论的时间,我们肯定不希望用户在发表评论时还得自己手动填写评论发表时间,这个时间应该自动生成。

创建了数据库模型就要迁移数据库,分别运行下面两条命令:

python manage.py makemigrations
python manage.py migrate

12.2.评论表单设计

下面开始编写评论表单代码。在 comments\ 目录下(和 models.py 同级)新建一个 forms.py 文件,用来存放表单代码,我们的表单代码如下:

comments/forms.py

from django import forms
from .models import Comment


class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ['name', 'email', 'url', 'text']

要使用 Django 的表单功能,我们首先导入 forms 模块。Django 的表单类必须继承自 forms.Form 类或者 forms.ModelForm 类。如果表单对应有一个数据库模型(例如这里的评论表单对应着评论模型),那么使用 ModelForm类会简单很多,这是 Django 为我们提供的方便。之后我们在表单的内部类 Meta 里指定一些和表单相关的东西。model = Comment 表明这个表单对应的数据库模型是 Comment 类。fields = ['name', 'email', 'url', 'text'] 指定了表单需要显示的字段,这里我们指定了 name、email、url、text 需要显示。

12.3.评论视图函数

当用户提交表单中的数据后,Django 需要调用相应的视图函数来处理这些数据,下面开始写我们视图函数处理逻辑:

comments/views.py

from django.shortcuts import render, get_object_or_404, redirect
from blog.models import Post

from .models import Comment
from .forms import CommentForm


def post_comment(request, post_pk):
    # 先获取被评论的文章,因为后面需要把评论和被评论的文章关联起来。
    # 这里我们使用了 Django 提供的一个快捷函数 get_object_or_404,
    # 这个函数的作用是当获取的文章(Post)存在时,则获取;否则返回 404 页面给用户。
    post = get_object_or_404(Post, pk=post_pk)

    # HTTP 请求有 get 和 post 两种,一般用户通过表单提交数据都是通过 post 请求,
    # 因此只有当用户的请求为 post 时才需要处理表单数据。
    if request.method == 'POST':
        # 用户提交的数据存在 request.POST 中,这是一个类字典对象。
        # 我们利用这些数据构造了 CommentForm 的实例,这样 Django 的表单就生成了。
        form = CommentForm(request.POST)

        # 当调用 form.is_valid() 方法时,Django 自动帮我们检查表单的数据是否符合格式要求。
        if form.is_valid():
            # 检查到数据是合法的,调用表单的 save 方法保存数据到数据库,
            # commit=False 的作用是仅仅利用表单的数据生成 Comment 模型类的实例,但还不保存评论数据到数据库。
            comment = form.save(commit=False)

            # 将评论和被评论的文章关联起来。
            comment.post = post

            # 最终将评论数据保存进数据库,调用模型实例的 save 方法
            comment.save()

            # 重定向到 post 的详情页,实际上当 redirect 函数接收一个模型的实例时,它会调用这个模型实例的 get_absolute_url 方法,
            # 然后重定向到 get_absolute_url 方法返回的 URL。
            return redirect(post)

        else:
            # 检查到数据不合法,重新渲染详情页,并且渲染表单的错误。
            # 因此我们传了三个模板变量给 detail.html,
            # 一个是文章(Post),一个是评论列表,一个是表单 form
            # 注意这里我们用到了 post.comment_set.all() 方法,
            # 这个用法有点类似于 Post.objects.all()
            # 其作用是获取这篇 post 下的的全部评论,
            # 因为 Post 和 Comment 是 ForeignKey 关联的,
            # 因此使用 post.comment_set.all() 反向查询全部评论。
            # 具体请看下面的讲解。
            comment_list = post.comment_set.all()
            context = {'post': post,
                       'form': form,
                       'comment_list': comment_list
                       }
            return render(request, 'blog/detail.html', context=context)
    # 不是 post 请求,说明用户没有提交数据,重定向到文章详情页。
    return redirect(post)

这个评论视图相比之前的一些视图复杂了很多,主要是处理评论的过程更加复杂。具体过程在代码中已有详细注释,这里仅就视图中出现了一些新的知识点进行讲解。

首先我们使用了 redirect 函数。这个函数位于 django.shortcuts 模块中,它的作用是对 HTTP 请求进行重定向(即用户访问的是某个 URL,但由于某些原因,服务器会将用户重定向到另外的 URL)。redirect 既可以接收一个 URL 作为参数,也可以接收一个模型的实例作为参数(例如这里的 post)。如果接收一个模型的实例,那么这个实例必须实现了 get_absolute_url 方法,这样 redirect 会根据 get_absolute_url 方法返回的 URL 值进行重定向。

另外我们使用了 post.comment_set.all() 来获取 post 对应的全部评论。 Comment 和Post 是通过 ForeignKey 关联的,回顾一下我们当初获取某个分类 cate 下的全部文章时的代码:Post.objects.filter(category=cate)。这里 post.comment_set.all() 也等价于 Comment.objects.filter(post=post),即根据 post 来过滤该 post 下的全部评论。但既然我们已经有了一个 Post 模型的实例 post(它对应的是 Post 在数据库中的一条记录),那么获取和 post 关联的评论列表有一个简单方法,即调用它的 xxx_set 属性来获取一个类似于 objects 的模型管理器,然后调用其 all 方法来返回这个 post 关联的全部评论。 其中 xxx_set 中的 xxx 为关联模型的类名(小写)。例如 Post.objects.filter(category=cate) 也可以等价写为 cate.post_set.all()

12.4.绑定url

视图函数需要和 URL 绑定,这里我们在 comment 应用中再建一个 urls.py 文件,写上 URL 模式:

comments/urls.py

from django.conf.urls import url

from . import views

app_name = 'comments'
urlpatterns = [
    url(r'^comment/post/(?P<post_pk>[0-9]+)/$', views.post_comment, name='post_comment'),
]

别忘了给这个评论的 URL 模式规定命名空间,即 app_name = 'comments'

最后要在项目的 blogprokect\ 目录的 urls.py 里包含 comments\urls.py 这个文件:

blogproject/urls.py

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'', include('blog.urls')),
    url(r'', include('comments.urls')),
]

12.5..更新文章详情页面的视图函数

我们可以看到评论表单和评论列表是位于文章详情页面的,处理文章详情页面的视图函数是 detail,相应地需要更新 detail,让它生成表单和从数据库获取文章对应的评论列表数据,然后传递给模板显示:

blog/views.py

import markdown

from django.shortcuts import render, get_object_or_404

+ from comments.forms import CommentForm
from .models import Post, Category

def detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    post.body = markdown.markdown(post.body,
                                  extensions=[
                                      'markdown.extensions.extra',
                                      'markdown.extensions.codehilite',
                                      'markdown.extensions.toc',
                                  ])
    # 记得在顶部导入 CommentForm
    form = CommentForm()
    # 获取这篇 post 下的全部评论
    comment_list = post.comment_set.all()

    # 将文章、表单、以及文章下的评论列表作为模板变量传给 detail.html 模板,以便渲染相应数据。
    context = {'post': post,
               'form': form,
               'comment_list': comment_list
               }
    return render(request, 'blog/detail.html', context=context)

12.6.在前段渲染页面

使用 Django 表单的一个好处就是 Django 能帮我们自动渲染表单。我们在表单的视图函数里传递了一个 form 变量给模板,这个变量就包含了自动生成 HTML 表单的全部数据。在 detail.html 中通过 form 来自动生成表单。删掉原来用于占位的 HTML 评论表单代码,即下面这段代码:

<form action="#" method="post" class="comment-form">
  <div class="row">
    <div class="col-md-4">
      <label for="id_name">名字:</label>
      <input type="text" id="id_name" name="name" required>
    </div>
    ...
  </div>    <!-- row -->
</form>

替换成如下的代码:

<form action="{% url 'comments:post_comment' post.pk %}" method="post" class="comment-form">
  {% csrf_token %}
  <div class="row">
    <div class="col-md-4">
      <label for="{{ form.name.id_for_label }}">名字:</label>
      {{ form.name }}
      {{ form.name.errors }}
    </div>
    <div class="col-md-4">
      <label for="{{ form.email.id_for_label }}">邮箱:</label>
      {{ form.email }}
      {{ form.email.errors }}
    </div>
    <div class="col-md-4">
      <label for="{{ form.url.id_for_label }}">URL:</label>
      {{ form.url }}
      {{ form.url.errors }}
    </div>
    <div class="col-md-12">
      <label for="{{ form.text.id_for_label }}">评论:</label>
      {{ form.text }}
      {{ form.text.errors }}
      <button type="submit" class="comment-btn">发表</button>
    </div>
  </div>    <!-- row -->
</form>

{{ form.name }}、{{ form.email }}、{{ form.url }} 等将自动渲染成表单控件,例如 <input> 控件。

{{ form.name.errors }}、{{ form.email.errors }} 等将渲染表单对应字段的错误(如果有的话),例如用户 email 格式填错了,那么 Django 会检查用户提交的 email 的格式,然后将格式错误信息保存到 errors 中,模板便将错误信息渲染显示。

12.7.显示评论内容

在 detail 视图函数我们获取了全部评论数据,并通过 comment_list 传递给了模板。和处理 index 页面的文章列表方式是一样的,我们在模板中通过 {% for %} 模板标签来循环显示文章对应的全部评论内容。

删掉占位用的评论内容的 HTML 代码,即如下的代码:

<ul class="comment-list list-unstyled">
  <li class="comment-item">
    <span class="nickname">追梦人物</span>
    <time class="submit-date">2017年3月12日 14:56</time>
    <div class="text">
      文章观点又有道理又符合人性,这才是真正为了表达观点而写,不是为了迎合某某知名人士粉丝而写。我觉得如果琼瑶是前妻,生了三孩子后被一不知名的女人挖了墙角,我不信谁会说那个女人是追求真爱,说同情琼瑶骂小三的女人都是弱者。
    </div>
  </li>
  ...
</ul>

替换成如下的代码:

<ul class="comment-list list-unstyled">
  {% for comment in comment_list %}
  <li class="comment-item">
    <span class="nickname">{{ comment.name }}</span>
    <time class="submit-date">{{ comment.created_time }}</time>
    <div class="text">
      {{ comment.text }}
    </div>
  </li>
  {% empty %}
  暂无评论
  {% endfor %}
</ul>

接下来尝试在详情页下的评论表单提交一些评论数据,可以看到详情页的评论列表处渲染了你提交的评论数据。

 12.8.完善跳转链接

导航栏有一个 Black & White 的 Logo,我们希望点击它就能回到首页面,只修修改一下超链接即可。打开 base.html,修改 Logo 处的超链接:

<header id="site-header">
  <div class="row">
      <div class="logo"><h1><a href="{% url 'blog:index' %}"><b>Black</b> &amp; White</a></h1>
      </div>
  ...
  </div>
</header>

另外导航栏还有一个首页导航按钮,也希望点击它就能回到首页面,还有一些跳转可以完善,比如文章标题下有分类、发布时间、作者、评论量、阅读量等信息,可以设置点击分类跳转到分类页面;点击评论量就跳转到文章详情页的评论处等,这些就交给你们自己完成了。

显示正确的评论量

可以看到,上面显示的评论数不对。我们需要修改

有两处地方显示的评论量,显示评论量的方法很简单。回顾一下我们是如何获取某篇 post 的下的评论列表的?我们使用的是 post.comment_set.all()all 方法返回该 post 关联的评论列表。此外模型管理器(comment_set 是一个特殊的模型管理器)还有一个 count 方法,返回的是数量,即 post 下有多少条评论,我们可以直接在模板中调用这个方法:{{ post.comment_set.count }}。将评论量替换成该模板变量就可以正确显示文章的评论数了。

 <span class="comments-link"><a href="#">{{ post.comment_set.count }} 评论</a></span>

 <h3>评论列表,共 <span>{{ post.comment_set.count }}</span> 条评论</h3>

结束了么?

我们通过一个博客实战项目,了解了 Django 基本的开发技术。包括如何编写模型(Model)、如何编写视图函数(View)、如何使用 Django 内置的模板系统(Template)以及如何配置路由(URL),这四大模块是 Django 开发的核心所在,现在我们已经能够基本掌握这些模块的使用方法了。

但是,还没完...

Django 提供的不仅仅是这些,我们的博客也不仅仅只有这些功能。如何实现标签云效果?如何给博客提供 RSS 订阅服务?如果实现文章搜索?如果网站需要提供用户系统,如何实现用户的注册登录?如何部署到服务器上让他人通过公网访问?这些需求都可以利用 Django 提供的 API 来实现。

另外,Django 还有海量的第三方应用来提供更加丰富的功能。比如当他人评论了我的文章时,如何收到通知提醒?网站需要提供新浪微博、微信等社交账号的登录等等,这些都可以借助 Django 第三方应用快速完成,而我们自己只需要写很少量的代码就可以了。

所以,让我们再接再厉,学习更多的 Django 开发技巧,为博客提供更多的功能吧!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏西安-晁州

vue.js应用开发笔记

看vue.js有几天了,之前也零零散散的瞅过,不过一直没有动手去写过demo,这几天后台事比较少,一直在讨论各种需求(其实公司对需求还是比较重视与严谨的,一个项...

4491
来自专栏用户2442861的专栏

无插件Vim编程技巧

相信大家看过《简明Vim教程》也玩了《Vim大冒险》的游戏了,相信大家对Vim都有一个好的入门了。我在这里把我日常用Vim编程的一些技巧列出来给大家看看,希望...

1141
来自专栏Java帮帮-微信公众号-技术文章全总结

kettle学习【大牛经验】

ETL是EXTRACT(抽取)、TRANSFORM(转换)、LOAD(加载)的简称,实现数据从多个异构数据源加载到数据库或其他目标地址,是数据仓库建设和维护中的...

3892
来自专栏不止是前端

从Highlight浅谈Webpack按需加载

最近有在使用 highlight.js 做代码的高亮展示,主要是展示对 SQL 语言的处理。看了看 highlight.js 的提供的相关代码

1321
来自专栏陈树义

Wins批处理基本语法

在Windows平台下,批处理可以和Wins系统很好地结合,处理一些简单的任务,比如:重复删除某种类型的文件;开机执行一些特定的命令等。 本文主要介绍批处理的基...

36510
来自专栏有趣的Python

6-Flask构建弹幕微电影网站-博客小项目学完flask基础

已上线演示地址: http://movie.mtianyan.cn 项目源码地址:https://github.com/mtianyan/movie_proj...

4194
来自专栏零基础使用Django2.0.1打造在线教育网站

零基础使用Django2.0.1打造在线教育网站(二十四):全局页面配置

努力与运动兼备~~~有任何问题可以加我好友或者关注微信公众号,欢迎交流,我们一起进步!

2463
来自专栏猿说1024

Linux 常用脚本

3143
来自专栏从零开始学自动化测试

Fiddler抓包7-post请求(json)

前言 上一篇讲过get请求的参数都在url里,post请求相对于get请求多了个body部分,本篇就详细讲解下body部分参数的几种形式。 一、body数据类...

2967
来自专栏Django中文社区

页面侧边栏:使用自定义模板标签

我们的博客侧边栏有四项内容:最新文章、归档、分类和标签云。这些内容相对比较固定,且在各个页面都会显示,如果像文章列表或者文章详情一样,从视图函数中获取然后传递给...

4016

扫码关注云+社区

领取腾讯云代金券