4.5 文章的评论功能
除浏览文章外,用户还可以对文章品头论足,发表自己的观点。本节需要实现的评论功能包括:
- 登录网站的用户有权限对任何文章发表评论;
- 评论内容都显示在文章的后面。
文章的评论功能如下图所示。

4.5.1 数据模型类和表单类
为了实现评论功能,需要建立相应的数据库表,用来存储评论内容。在./article/models.py文件中创建与评论功能相关的数据模型类并编辑这个文件,代码如下。
1 from django.db import models 2 from django.contrib.auth.models import User 3 4 class Comment(models.Model): 5 article = models.ForeignKey(ArticlePost,on_delete=models.CASCADE,related_name="comments") #① 6 commentator = models.CharField(max_length=90) 7 body = models.TextField() 8 created = models.DateTimeField(auto_now_add=True) 9 10 class Meta: 11 ordering = ('-created',) #② 12 13 def __str__(self): 14 return "Comment by {0} on {1}".format(self.commentator.username,self.article)
在语句①中通过ForeignKey()将本数据模型与ArticlePost建立关系,它们属于多对一的关系,即一篇文章可以有多篇评论。
语句②是容易出错的地方,请务必注意这里使用的是元组类型,不要丢掉后面的逗号;符号意味着按照created的倒序排列。
数据模型类编写好之后,就可以实施同步数据库的操作了。为了满足好奇心,可以查看一下数据库的基本结构,如下图所示。


为了能够在文章的后面给用户显示一个填写评论的输入框,还要建立表单类。在./article/forms.py文件中编写表单类,代码如下。
1 from django import forms 2 from .models import Comment 3 4 class CommentForm(forms.ModelForm): 5 class Meta: 6 model = Comment 7 fields = ("commentator","body",)
基础工作完成,下面就要实现具体的功能。
4.5.2 实现评论功能
评论功能是在用户阅读文章的页面中,所以需要再次编辑./article/list_view.py文件中的article_detail()函数。可以先看一下这个函数目前的样子,为了实现评论功能,要对此函数增加内容。
首先./article/list_view.py文件中引入Comment类和CommentForm类,代码如下。
1 from .models import Comment 2 from .forms import CommentForm 3 4 def read_article(request,id,slug): 5 article = get_object_or_404(ArticlePost, id=id, slug=slug) 6 total_views = r.incr("article:{}:views".format(article.id)) 7 r.zincrby('article_ranking',article.id,1) 8 article_ranking = r.zrange('article_ranking',0,-1,desc=True)[:10] 9 article_ranking_ids = [int(id) for id in article_ranking] 10 most_viewed = list(ArticlePost.objects.filter(id__in=article_ranking_ids)) 11 most_viewed.sort(key=lambda x:article_ranking_ids.index(x.id)) 12 13 if request.method == "POST": #① 14 comment_form = CommentForm(data=request.POST) 15 if comment_form.is_valid(): 16 new_comment = comment_form.save(commit=False) 17 new_comment.article = article 18 new_comment.save() 19 else: 20 comment_form = CommentForm() 21 22 return render(request,"article/list/article_detail.html",{"article":article,"total_views":total_views, 23 "most_viewed":most_viewed,"comment_form":comment_form})
从语句①开始是处理前端以POST形式提交过来的表单数据,并且在验证(comment_form.is_valid())通过之后,保存到数据库。
编辑模板文件./article/list/article_detail.html,在“点赞本文的读者”功能代码块后面增加如下内容。
1 <div> 2 <p class="text-center"><strong>点赞文本的读者</strong></p> 3 {% for user in article.users_like.all %} 4 <p class="text-center">{{ user.username }}</p> 5 {% empty %} 6 <p class="text-center">还没有人对此文章表态</p> 7 {% endfor %} 8 </div> 9 <hr> 10 <div> 11 <h3><span class="glyphicon glyphicon-bullhorn"></span>本文有{{ comments.count }}评论</h3> 12 {% for comment in article.comments.all %} #② 13 <div> 14 <p><strong>{{ comment.commentator }}</strong>说:</p> 15 <p style="margin-left:40px;">{{ comment.body }}</p> 16 </div> 17 {% empty %} 18 <p>没有评论</p> 19 {% endfor %} 20 21 <h3><span class="glyphicon glyphicon-send"></span>看文章,发评论,不要沉默</h3> 22 <form action="." method="post" class="form-horizontal" role="form">{%csrf_token %} 23 <div class="form-group"> 24 <label for="inputEmail3" class="col-sm-2 control-label">评论员</label> 25 <div class="col-sm-10"> 26 {{ comment_form.commentator }} 27 </div> 28 </div> 29 <div class="form-group"> 30 <label for="inputEmail3" class="col-sm-2 control-label">评论</label> 31 <div class="col-sm-10"> 32 {{ comment_form.body }} 33 </div> 34 </div> 35 <div class="form-group"> 36 <div class="col-sm-offest-2 col-sm-10"> 37 <p><input type="submit" name="" value="发评论" class="btn btn-primary"></p> 38 </div> 39 </div> 40 </form> 41 </div> 42 </div>
上述新增的代码主要有两个功能,一个是显示已有的评论,另一个是显示发表评论的表单。
语句②中以article.comments.all得到文章的所有评论,请注意这种模板语言的用法,类似与Django的QuerySet语句。
此处,当用户提交表单时,没有使用我们熟悉的Ajax方式,而是将<input>的类型设置为submit,可以直接提交到多规定的URL。这种方法索然简单,单页存在瑕疵。不妨在调试中观察。特别鼓励将这里修改为Ajax方式,具体修改方法可以参照本书前面的有关章节内容。
模板文件编写好之后,就可以看演示效果了。不过,要注意的是,因为我们前面使用了Redis数据库,要保证它处于运行状态,并且确保Django服务也启动了。浏览某篇文章,可以在底部进行评论,如下图所示。

4.5.3 知识点
1、模型:Meta
在数据模型类的内部,有时候会定义class Meta,这个类的名字很有特点,读者如果熟悉Python,就会想到在Python中有metaclass(元类),但在数据模型类内部定义的Meta和Python中的metaclass是有区别的,我们把在数据模型类内部定义的名为Meta的类称为内部类。
内部类的作用是什么?在Python中,类的内部以class Meta方式来写一个内部类,其主要作用是让该类的不同实例共用一个属性值。为了便于理解,请看下面的实例。

语句①定义了一个类Foo,在其内部有内部类Meta,这个内部类中只有一个变量name,并已经赋值了。然后建立了两个实例teacher和good_teacher,一般情况下,实例teacher的属性值和实例good_teacher的属性值是不会相互影响的,而是相对独立的。语句②得到了实例teacher的内部类中的属性name的值。语句③对另外一个实例good_teacher的内部类属性name重新赋值。语句④将该值共享给了teacher这个实例的内部类的name属性。这就是内部类的作用。
Django数据模型中的内部类也规定了该数据模型不论哪个实例,都应该具有的行为。
在内部类中,通过声明一些属性的值,让该数据模型具有某些指定的行为,比如本节Comment类中的代码。
1 class Meta; 2 ordering = ('-craeted',)
本来Comment的实例(数据库表中的记录)有默认的排序字段,但这里内部类中重新规定了排序字段和排序原则,即按照created的值的倒序排列。
内部类中的变量不是数据模型类的字段,官方文档中用“anything that's not a field”来说明了其意义。
- abstract
用于定义当前的模型类是不是一个抽象类。抽象类不对应数据库表。一般用它来归纳一些公共属性字段,继承它的子类可以继承这些字段。
- app_label
如果当前的数据模型不在INSTALLED_APPS(settings.py中)所注册的应用中,那么久需要用app_label来声明本数据模型所属于的应用。比如在其他地方写了一个模型类,而这个模型类是属于myapp的,就需要设置app_label='myapp'。
- db_table
用于指定自定义数据库的表名。在默认状态下,完成数据迁移之后,会按照Django的规定生成数据库表名,而这个属性提供了一种自定义表名的途径。
- index_together
以列表类型的值声明索引字段名称,例如index_together = ["pub_date","deadline"]。
浙公网安备 33010602011771号