除浏览文章外,用户还可以对文章品头论足,发表自己的观点。本节需要实现的评论功能包括:

  • 登录网站的用户有权限对任何文章发表评论;
  • 评论内容都显示在文章的后面。

文章的评论功能如下图所示。

如果对文章记录了用户是否评论用smember 方案 bigkey 大不大 对文章进行评论_内部类

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的倒序排列。

数据模型类编写好之后,就可以实施同步数据库的操作了。为了满足好奇心,可以查看一下数据库的基本结构,如下图所示。

如果对文章记录了用户是否评论用smember 方案 bigkey 大不大 对文章进行评论_数据模型_02

如果对文章记录了用户是否评论用smember 方案 bigkey 大不大 对文章进行评论_数据模型_03

为了能够在文章的后面给用户显示一个填写评论的输入框,还要建立表单类。在./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服务也启动了。浏览某篇文章,可以在底部进行评论,如下图所示。

如果对文章记录了用户是否评论用smember 方案 bigkey 大不大 对文章进行评论_字段_04

 

4.5.3 知识点

1、模型:Meta

在数据模型类的内部,有时候会定义class Meta,这个类的名字很有特点,读者如果熟悉Python,就会想到在Python中有metaclass(元类),但在数据模型类内部定义的Meta和Python中的metaclass是有区别的,我们把在数据模型类内部定义的名为Meta的类称为内部类。

内部类的作用是什么?在Python中,类的内部以class Meta方式来写一个内部类,其主要作用是让该类的不同实例共用一个属性值。为了便于理解,请看下面的实例。

如果对文章记录了用户是否评论用smember 方案 bigkey 大不大 对文章进行评论_数据模型_05

语句①定义了一个类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"]。