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