评论在数据库中的表示
评论和博客文章没有太大区别,都有正文、作者和时间戳,而且在这个特定实现中都使用Markdown语法编写,下表是comments表的图集以及和其他数据表之间的关系
评论属于某篇博客文章,因此定义了一个从posts表到comments表的一对多关系,使用这个关系可以获取某篇特定博客文章的评论列表
comments表还和users表之间有一对多关系,通过这个关系可以获取用户发表的所有评论,还能间接知道用户发表了多少篇评论,用户发表的评论数量可以显示在用户资料页中,Comment模型的定义如下
# app/models.py
#...
class Comment(db.Model):
__tablename__ = 'comments'
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.Text)
body_html = db.Column(db.Text)
timestamp = db.Column(db.DateTime, index=True ,default=datetime.utcnow)
disabled = db.Column(db.Boolean)
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
post_id = db.Column(db.Integer, db.ForeignKey('posts.id'))
@staticmethod
def on_changed_body(target, value, oldvalue, initiator):
allowed_tags = ['a', 'abbr', 'acronym', 'b', 'cide', 'em', 'i', 'strong']
target.body_html = bleach.linkify(bleach.clean(
markdown(value, output_format='html'),
tags = allowed_tags, strip=True))
db.event.listen(Comment.body, 'set', Comment.on_changed_body)
Comment模型的属性几乎和Post模型一样,不过多了一个disabled字段,这是个布尔值字段,协管员通过这个字段查禁不当评论,和博客文章一样,评论也定义了一个事件,在修改body字段内容时触发,自动把Markdown文本转换成HTML,转换过程和之前的博客文章一样,不过评论相对较短,而且对Markdown中允许使用的HTML标签要求更严格,要删除与段落相关的标签,只留下格式化字符的标签
为了完成对数据库的修改,User和Post模型还要建立与comments表的一对多关系,如下:
# app/models.py
class User(db.model):
#...
comments = db.relationship('Comment', backref='author', lazy='dynamic')
class Post(db.Model):
#...
comments = db.relationship('Comment', backref='post', lazy='dynamic')
提交和显示评论
在这个程序中,评论要显示在单篇博客文章页面中,这个页面在之前添加固定链接时已经创建,在这个页面中还要有一个提交评论的表单,用来输入评论的简单表单如下:
# app/main/forms.py
class CommentForm(Form):
body = StringFIeld('', validators=[Required()])
submit = SubmitField('Submit')
下例是为了支持评论而更新的/post/<int:id>
路由
# app/main/views.py
# ...
@main.route('/post/<int:id>')
def post(id):
post = Post.query.get_or_404(id)
form = CommentForm()
if form.validate_on_submit():
comment = Comment(body=form.body.data,
post=post,
author=current_user._get_curernt_object())
db.session.add(comment)
flash('Your comment has been published.')
return redirect(url_for('.post', id=post.id, page=-1))
page = request.args.get('page', 1, type=int)
if page == -1:
page = (post.comments.count() - 1) / \
current_app.config['FLASKY_COMMENTS_PER_PAGE'] + 1
pagination = post.comments.order_by(Comment.timestamp.asc()).paginate(
page, per_page=current_app.config['FLASKY_COMMENTS_PER_PAGE'],
error_out=False)
comments = pagination.items
return render_template('post.html', posts=[post], form=form,
comments=comments, pagination=pagination)
这个视图函数实例化了一个评论表单,并将其转入post.html
模板,以便渲染,提交表单后,插入新评论的逻辑和处理博客文章的过程差不多,和Post模型一样,评论的author
字段也不能直接设为current_user
,因为这个变量是上下文代理对象,真正的User对象要使用字段表达式current_user._get_current_object()
获取
评论按照时间戳顺序排列,新评论显示在列表的底部,提交评论后,请求结果是一个重定向,转回之前的URL,但是在url_for()
函数的参数中把page
设为-1
,这是个特殊的页数,用来请求评论的最后一页,所以刚提交的评论才会出现在页面中,程序从查询字符串中获取页数,发现值为-1
时,会计算评论的总量和总页数,得出真正要实现的页数
文章的评论列表通过post.comments
一对多关系获取,按照时间戳顺序进行排列,再使用博客文章相同的技术分页显示,评论列表对象和分页对象都传入了模板,以便渲染,FLASKY_COMMENTS_PER_PAGE
配置变量也被加入config.py
中,用来控制每页显示的评论数量
评论的渲染过程在新模板_comments.html
中进行,类似于_posts.html
,但使用的CSS类不同,_comments.html
模板要引入post.html
中,放在文章正文下方,后面再显示分页导航,新模板内容如下:
<ul class="comments">
{% for comment in comments %}
<li class="comment">
<div class="comment-thumbnail">
<a href="{{ url_for('.user', username=comment.author.username) }}">
<img class="img-rounded profile-thumbnail" src="{{ comment.author.gravatar(size=40) }}">
</a>
</div>
<div class="comment-content">
<div class="comment-date">{{ moment(comment.timestamp).fromNow() }}</div>
<div class="comment-author"><a href="{{ url_for('.user', username=comment.author.username) }}">{{ comment.author.username }}</a></div>
<div class="comment-body">
{% if comment.body_html %}
{{ comment.body_html | safe }}
{% else %}
{{ comment.body }}
{% endif %}
</div>
</div>
</li>
{% endfor %}
</ul>
为了完善功能,我们还要在首页和资料页中加上指向评论页面的链接,如下:
# app/templates/_posts.html
# ...
<a href="{{ url_for('.post', id=post.id) }}#comments">
<span class="label label-primary">
{{ post.comments.count() }} Comments
</span></a>
注意链接文本中显示评论数量的方法,评论数量可以使用SQLAlchemy提供的count()
过滤器轻易的从posts和comments表的一对多关系中获取
指向评论页的链接结构也值得一说,这个链接的地址是在文章的固定链接后面加上一个#comments
后缀,这个后缀称为URL片段,用于指定加载页面后滚动条所在的初始位置,Web浏览器会寻找id等于URL片段的元素并滚动页面,让这个元素显示在窗口顶部,这个初始位置被设为post.html
模板中评论区的标题,即<h4 id="comments">Comments<h4>
除此之外,分页导航所用的宏也要做某些改动,评论的分页导航链接也要加上#comments
片段,因此在post.html
模板中调用宏时,传入片段参数