文章目录
- 0.思路引导
- 1.创建"评论"应用
- 2.设计"评论"的数据库模型
- 3.注册"评论"模型到 admin
- 4.设计“评论”表单
- 5.展示评论表单
- 6.“评论”视图函数
- 7.绑定 URL
- 8.向读者发送是否“评论”成功的状态
- 9.详情页底部显示“评论”内容
0.思路引导
本文将创建一个新的应用,即在博客详情页的底部,增加评论功能。
效果显示如下所示,具体的操作将从头到尾进行记录。
1.创建"评论"应用
1)进入到项目根目录,然后输入如下命令创建一个新的应用:
pipenv run python manage.py startapp comments
2)在 settings.py 里注册这个应用,django 才知道这是一个应用
文件位置:blogproject/settings.py
...
INSTALLED_APPS = [...'blog.apps.BlogConfig', # 注册 blog 应用'comments.apps.CommentsConfig', # 注册 comments 应用
]v
...
3)注意这里注册的是 CommentsConfig 类,同时进行配置,让 comment 应用在 django 的 admin 后台显示中文名字。
文件位置:comments/app.py
from django.apps import AppConfigclass CommentsConfig(AppConfig):name = 'comments'verbose_name = '评论'
2.设计"评论"的数据库模型
1)编写数据库模型文件
文件位置:comments/models.py
from django.db import models
from django.utils import timezoneclass Comment(models.Model):name = models.CharField('名字', max_length=50)email = models.EmailField('邮箱')url = models.URLField('网址', blank=True)text = models.TextField('内容')created_time = models.DateTimeField('创建时间', default=timezone.now)post = models.ForeignKey('blog.Post', verbose_name='文章', on_delete=models.CASCADE)class Meta:verbose_name = '评论'verbose_name_plural = verbose_namedef __str__(self):return '{}: {}'.format(self.name, self.text[:20])
2)迁移数据库
在项目根目录下分别运行下面两条命令:
pipenv run python manage.py makemigrations
pipenv run python manage.py migrate
3.注册"评论"模型到 admin
既然已经创建了模型,我们就可以将它注册到 django admin 后台,方便管理员用户对评论进行管理
文件位置:comments/admin.py
from django.contrib import admin
from .models import Commentclass CommentAdmin(admin.ModelAdmin):list_display = ['name', 'email', 'url', 'post', 'created_time']fields = ['name', 'email', 'url', 'text', 'post']admin.site.register(Comment, CommentAdmin)
实际效果展示如下:
4.设计“评论”表单
思维逻辑:
1)表单是用来收集并向服务器提交用户输入的数据的,是用户在我们博客网站上发表评论的过程。
2)当用户想要发表评论时,他找到我们给他展示的一个评论表单(在文章详情页的底部就有一个评论表单),然后根据表单的要求填写相应的数据。
3)之后用户点击评论按钮,这些数据就会发送给某个 URL。我们知道每一个 URL 对应着一个 django 的视图函数,于是 django 调用这个视图函数,我们在视图函数中,对用户通过表单提交上来的数据进行处理,比如验证数据的合法性并且保存数据到数据库中,那么用户的评论就被 django 处理了。
4)如果通过表单提交的数据存在错误,那么我们把错误信息返回给用户,并在前端重新渲染表单,要求用户根据错误信息修正表单中不符合格式的数据,再重新提交。
文件位置:comments/forms.py
from django import forms
from .models import Commentclass CommentForm(forms.ModelForm):class Meta:model = Commentfields = ['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 需要显示。
5.展示评论表单
表单类已经定义完毕,现在的任务是在文章的详情页下方将这个表单展现给用户,用户可以通过这个表单填写评论数据,从而发表评论。
那么怎么展现一个表单呢?django 会根据表单类的定义自动生成表单的 HTML 代码,我们要做的就是实例化这个表单类,然后将表单的实例传给模板,让 django 的模板引擎来渲染这个表单。那怎么将表单类的实例传给模板呢?
1)因为表单出现在文章详情页,一种想法是修改文章详情页 detail 视图函数,在这个视图中实例化一个表单,然后传递给模板。然而这样做的一个缺点就是需要修改 detail 视图函数的代码,而且 detail 视图函数的作用主要就是处理文章详情;
2)一个视图函数最好不要让它做太多杂七杂八的事情。另外一种想法是使用自定义的模板标签,我们在 页面侧边栏曾经使用自定义模板标签,这里我们使用自定义模板标签的方法,来渲染表单页面。
在 comments\ 文件夹下新建一个 templatetags 文件夹,然后创建 init.py 文件使其成为一个包,再创建一个 comments_extras.py 文件用于存放模板标签的代码。然后我们定义一个 inclusion_tag 类型的模板标签,用于渲染评论表单。
文件位置:comments\comments_extras.py
from django import template
from ..forms import CommentFormregister = template.Library()@register.inclusion_tag('comments/inclusions/_form.html', takes_context=True)
def show_comment_form(context, post, form=None):if form is None:form = CommentForm()return {'form': form,'post': post,}
从定义可以看到,show_comment_form 模板标签使用时会接受一个 post(文章 Post 模型的实例)作为参数,同时也可能传入一个评论表单 CommentForm 的实例 form,如果没有接受到评论表单参数,模板标签就会新创建一个 CommentForm 的实例(一个没有绑定任何数据的空表单)传给模板,否则就直接将接受到的评论表单实例直接传给模板,这主要是为了复用已有的评论表单实例。
然后在 templates/comments/inclusions 目录下(没有就新建)新建一个 _form.html 模板,写上代码:
<form action="{% url 'comments: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 }}">{{ form.name.label }}:</label>{{ form.name }}{{ form.name.errors }}</div><div class="col-md-4"><label for="{{ form.email.id_for_label }}">{{ form.email.label }}:</label>{{ form.email }}{{ form.email.errors }}</div><div class="col-md-4"><label for="{{ form.url.id_for_label }}">{{ form.url.label }}:</label>{{ form.url }}{{ form.url.errors }}</div><div class="col-md-12"><label for="{{ form.text.id_for_label }}">{{ form.text.label }}:</label>{{ form.text }}{{ form.text.errors }}<button type="submit" class="comment-btn">发表</button></div></div> <!-- row -->
</form>
然后我们就可以在 detail.html 中使用这个模板标签来渲染表单了,注意在使用前记得先 {% load comments_extras %} 这个模块。而且为了避免可能的报错,最好重启一下开发服务器。
{% extends 'base.html' %}
{% load comments_extras %}
...<h3>发表评论</h3>
{% show_comment_form post %}
6.“评论”视图函数
当用户提交表单中的数据后,django 需要调用相应的视图函数来处理这些数据,下面开始写我们视图函数处理逻辑:
from blog.models import Post
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.http import require_POSTfrom .forms import CommentForm@require_POST
def comment(request, post_pk):# 先获取被评论的文章,因为后面需要把评论和被评论的文章关联起来。# 这里我们使用了 django 提供的一个快捷函数 get_object_or_404,# 这个函数的作用是当获取的文章(Post)存在时,则获取;否则返回 404 页面给用户。post = get_object_or_404(Post, pk=post_pk)# django 将用户提交的数据封装在 request.POST 中,这是一个类字典对象。# 我们利用这些数据构造了 CommentForm 的实例,这样就生成了一个绑定了用户提交数据的表单。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)# 检查到数据不合法,我们渲染一个预览页面,用于展示表单的错误。# 注意这里被评论的文章 post 也传给了模板,因为我们需要根据 post 来生成表单的提交地址。context = {'post': post,'form': form,}return render(request, 'comments/preview.html', context=context)
首先视图函数被 require_POST 装饰器装饰,从装饰器的名字就可以看出,其作用是限制这个视图只能通过 POST 请求触发,因为创建评论需要用户通过表单提交的数据,而提交表单通常都是限定为 POST 请求,这样更加安全。
另外我们使用了 redirect 快捷函数。这个函数位于 django.shortcuts 模块中,它的作用是对 HTTP 请求进行重定向(即用户访问的是某个 URL,但由于某些原因,服务器会将用户重定向到另外的 URL)。redirect 既可以接收一个 URL 作为参数,也可以接收一个模型的实例作为参数(例如这里的 post)。
如果接收一个模型的实例,那么这个实例必须实现了 get_absolute_url 方法,这样 redirect 会根据 get_absolute_url 方法返回的 URL 值进行重定向,如下:
与此同时,视图函数中还对读者输入的数据格式进行判断,如果提交的数据合法,我们就将评论数据保存到数据库,否则说明用户提交的表单包含错误,我们将渲染一个 preview.html 页面,来展示表单中的错误,以便用户修改后重新提交。
文件位置:templates/comments/preview.html
{% extends 'base.html' %}
{% load comment_extras %}{% block main %}{% show_comment_form post form %}
{% endblock main %}
这里还是使用 show_comment_form 模板标签来展示一个表单,然而不同的是,这里我们传入由视图函数 comment 传来的绑定了用户提交的数据的表单实例 form,而不是渲染一个空表单。
因为视图函数 comment 中的表单实例是绑定了用户提交的评论数据,以及对数据进行过合法性校验的表单,因此当 django 渲染这个表单时,会连带渲染用户已经填写的表单数据以及数据不合法的错误提示信息,而不是一个空的表单了。
例如下图,我们提交的数据中 email 格式不合法,表单校验了数据格式,然后渲染错误提示:
7.绑定 URL
在 comment 应用中再建一个 urls.py 文件,写上 URL 模式:
文件位置: comments/urls.py
from django.urls import pathfrom . import viewsapp_name = 'comments'
urlpatterns = [path('comment/<int:post_pk>', views.comment, name='comment'),
]
然后在项目的 blogproject\ 目录的 urls.py 里包含 comments\urls.py 这个文件:
文件位置:blogproject/urls.py
from django.contrib import admin
from django.urls import path,includeurlpatterns = [path(r'admin/', admin.site.urls),path(r'', include('blog.urls')),path(r'', include('comments.urls')),
]
8.向读者发送是否“评论”成功的状态
第六部分中,测试提交评论功能时,尝试输入非法格式的数据,例如将邮箱输入为 xxx@xxx,那么评论视图在校验表单数据合法性时,发现邮箱格式不符,就会渲染 preview 页面,展示表单中的错误,将邮箱修改为正确的格式后,再次点击发表,页面就跳转到了被评论文章的详情页,说明视图正确执行了保存表单数据到数据库的逻辑。
不过这里有一点不好的地方就是,评论成功后页面直接跳转到了被评论文章的详情页,没有任何提示,用户也不知道评论究竟有没有真的成功。这里我们使用 django 自带的 messages 应用来给用户发送评论成功或者失败的消息。
django 默认已经为我们做好了 messages 的相关配置,直接用即可。
两个地方需要发送消息,第一个是当评论成功,即评论数据成功保存到数据库后,因此在 comment 视图中加一句。
from django.contrib import messagesif 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 的导航条代码下增加如下代码:
文件位置:templates/base.html
<header>...
</header>
{% if messages %}{% for message in messages %}<div class="alert alert-{{ message.tags }} alert-dismissible" role="alert"><button type="button" class="close" data-dismiss="alert" aria-label="Close"><spanaria-hidden="true">×</span></button>{{ message }}</div>{% endfor %}
{% endif %}
这里 django 会通过全局上下文自动把 messages 变量传给模板,这个变量里存储我们发送的消息内容,然后就是循环显示消息了。这里我们使用了 bootstrap 的一个 alert 组件,为其设置不同的 class 会显示不同的颜色,所以之前添加消息时传入的 extra_tags 就派上了用场。比如这里 alert-{{ message.tags }},当传入的是 success 时,类名就为 alert-success,这时显示的消息背景颜色就是绿色,传入的是 dangerous,则显示的就是红色。
评论发布成功和失败的消息效果如下图:
9.详情页底部显示“评论”内容
为了不改动已有的视图函数的代码,评论数据我们也使用自定义的模板标签来实现。
文件位置:comments\comments_extras.py
@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,}
文件位置: templates/comments/inclusions/_list.html
<h3>评论列表,共 <span>{{ comment_count }}</span> 条评论</h3>
<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" datetime="{{ comment.created_time }}">{{ comment.created_time }}</time><div class="text">{{ comment.text|linebreaks }}</div></li>{% empty %}暂无评论{% endfor %}
</ul>
最后在detail.html 中,将此前占位用的评论模板替换为模板标签渲染的内容:
<h3>发表评论</h3>
{% show_comment_form post %}
<div class="comment-list-panel">{% show_comments post %}
</div>
访问文章详情页,即可看到已经发表的评论列表。效果展示如下: