本节实现用户评论功能
一. 评论在数据库中的表示
评论包含正文、作者和时间戳;用户和评论,文章和评论均为一对多的关系,我们需要在多这一次侧定义外键:
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, default=datetime.utcnow, index=True)
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'))
disabled字段:协管员通过这个字段查禁不当评论。与博客文章一样,我们也需要定义一个事件,在修改body字段内容时触发,自动把Markdown文本转换为HTML:
@db.event.listens_for(Comment.body, 'set', named=True)
def on_change_body(**kwargs):
allowed_tags = ['a', 'abbr', 'acronym', 'b', 'code', 'em', 'i', 'strong']
kwargs['target'].body_html = bleach.linkify(bleach.clean(markdown(kwargs['value'], output_format='html'),tags=allowed_tags, strip=True))
注意:
- 装饰器中将named设为True,被装饰函数才可以接受关键字传参;
- MarkDown中允许使用的HTML标签要求更严格,要删除与段落相关的标签,只留下格式化字符的标签;
User模型与Comment模型做为1这一侧,还需要定义关系(通常我们在多一侧定义外键后,选择在一这一侧定义关系):
class User(UserMixin, db.Model):
__tablename__ = "users"
...
comments = db.relationship('Comment', backref='author', lazy='dynamic')
class Post(db.Model):
__tablename__ = 'posts'
...
comments = db.relationship('Comment', backref='post', lazy='dynamic')
二. 提交和显示评论
app/main/forms.py 评论输入表单:
class CommentForm(FlaskForm):
body = StringField('', validators=[DataRequired()])
submit = SubmitField('Submit')
为了支持表单提交,/post/<int:id>路由要做些修改:
@main.route('/post/<int:id>', methods=['GET', 'POST'])
def post(id):
post = Post.query.get_or_404(id)
form = CommentForm()
if current_user.can(Permission.COMMENT) and form.validate_on_submit():
comment = Comment(body=form.body.data, post=post, author=current_user._get_current_object())
db.session.add(comment)
db.session.commit()
flash('Your comment has been published.')
return redirect(url_for('.post', id=post.id, page=-1))
page = request.args.get('page', 1, type=int)
per_page = current_app.config['FLASKY_COMMENTS_PER_PAGE']
if page == -1:
# 计算总页数
page = (post.comments.count() - 1) // per_page + 1
pagination = post.comments.order_by(Comment.timestamp.asc()).paginate(page, per_page, error_out=False)
comments = pagination.items
return render_template('post.html', posts=[post], form=form, comments=comments, pagination=pagination)
注意:
- 实例化Comment时使用的author字段不能直接设为current_user,因为这个变量是上下文代理对象。真正的User对象要使用表达式current_user._get_current_object()获取;
- 评论按时间戳正序排列,默认显示第一页(即最早的评论);当用户发表新评论后,重定向到当前页面时,将page设为-1,显示最后一页。此时我们需要计算总评论量和总页数,以便得出真正要显示的页数;
最后,我们在首页和资料页分别添加指向评论页面的链接:
<div class="post-footer">
....
<a href="{{ url_for('.post', id=post.id) }}#comments">
<span class="label label-primary">{{ post.comments.count() }} Comments</span>
</a>
</div>
注意:指向评论页的链接结构中包含“#comments”后缀,这个后缀称为URL片段,用于指定加载页面后滚动条所在的初始位置。此处滚动条的初始位置被设为"Comments"标题,其中HTML代码为<h4 id="#comments">Comments</h4>;
在用户资料页面: