最后要在项目的 blogproject 目录的 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')), ]可以测试一下提交评论的功能了,首先尝试输入非法格式的数据,例如将邮箱输入为 xxx@xxx,那么评论视图在校验表单数据合法性时,发现邮箱格式不符,就会渲染 preview 页面,展示表单中的错误,将邮箱修改为正确的格式后,再次点击发表,页面就跳转到了被评论文章的详情页,说明视图正确执行了保存表单数据到数据库的逻辑。
不过这里有一点不好的地方就是,评论成功后页面直接跳转到了被评论文章的详情页,没有任何提示,用户也不知道评论究竟有没有真的成功。这里我们使用 django 自带的 messages 应用来给用户发送评论成功或者失败的消息。
发送评论消息django 默认已经为我们做好了 messages 的相关配置,直接用即可。
两个地方需要发送消息,第一个是当评论成功,即评论数据成功保存到数据库后,因此在 comment 视图中加一句。
from django.contrib import messages if form.is_valid(): ... # 最终将评论数据保存进数据库,调用模型实例的 save 方法 comment.save() messages.add_message(request, messages.SUCCESS, '评论发表成功!', extra_tags='success') return redirect(post)这里导入 django 的 messages 模块,使用 add_message 方法增加了一条消息,消息的第一个参数是当前请求,因为当前请求携带用户的 cookie,django 默认将详细存储在用户的 cookie 中。第二个参数是消息级别,评论发表成功的消息设置为 messages.SUCCESS,这是 django 已经默认定义好的一个整数,消息级别也可以自己定义。紧接着传入消息的内容,最后 extra_tags 给这条消息打上额外的标签,标签值可以在展示消息时使用,比如这里我们会把这个值用在模板中的 HTML 标签的 class 属性,增加样式。
同样的,如果评论失败了,也发送一条消息:
# 检查到数据不合法,我们渲染一个预览页面,用于展示表单的错误。 # 注意这里被评论的文章 post 也传给了模板,因为我们需要根据 post 来生成表单的提交地址。 context = { 'post': post, 'form': form, } messages.add_message(request, messages.ERROR, '评论发表失败!请修改表单中的错误后重新提交。', extra_tags='danger')发送的消息被缓存在 cookie 中,然后我们在模板中获取显示即可。显示消息比较好的地方是在导航条的下面,我们在模板 base.html 的导航条代码下增加如下代码:
<header> ... </header> {% if messages %} {% for message in messages %} <div role="alert"> <button type="button" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> {{ message }} </div> {% endfor %} {% endif %}这里 django 会通过全局上下文自动把 messages 变量传给模板,这个变量里存储我们发送的消息内容,然后就是循环显示消息了。这里我们使用了 bootstrap 的一个 alert 组件,为其设置不同的 class 会显示不同的颜色,所以之前添加消息时传入的 extra_tags 就派上了用场。比如这里 alert-{{ message.tags }},当传入的是 success 时,类名就为 alert-success,这时显示的消息背景颜色就是绿色,传入的是 dangerous,则显示的就是红色。
评论发布成功和失败的消息效果如下图:
显示评论内容为了不改动已有的视图函数的代码,评论数据我们也使用自定义的模板标签来实现。模板标签代码如下:
@register.inclusion_tag('comments/inclusions/_list.html', takes_context=True) def show_comments(context, post): comment_list = post.comment_set.all().order_by('-created_time') comment_count = comment_list.count() return { 'comment_count': comment_count, 'comment_list': comment_list, }我们使用了 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()。