最近忙着搭建自己的博客网站忙的焦头烂额,其中就包括文章底下评论以及评论回复的功能实现。在网上找到很多资料并不能解决,最后通过蛛丝马迹和我自己的才华(笑)解决了这个问题,所以今天就像跟大家分享一下此部分功能的踩坑经验。
一、数据库设计
此部分是最难的地方,思考一下,一篇文章有很多评论,一个user对应一句评论,每一句评论底下对应相对的对此评论的回复,每一句回复又与回复对象user相对应。
好吧,总结一下:
- “评论”与“回复”两个表可以用一个,只是对象不同,评论的对象为文章(作者),回复的对象为评论的user
- 每一句“回复”Post对应两个user,一个user是发起者,一个user是回复的对象。
- 文章Articles与“回复”Post一对多关系。
画个图:
二、相应的model代码设计
除了上述问题之外,还要考虑三个基本表的基本属性。另外还需要考虑的:
1.在post表中增加一个布尔类型的字段,以便设置评论是否为私密文章(私密文章后面再考虑)。
2.post表中增加reply_id以对应回复的对象是哪一条评论(post)
3.post表增加timestamp以记录评论的时间
class Post(db.Model):#评论,一个用户对应多个评论
__tablename__='posts'
d = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'),index=True)#外键
user_to_id = db.Column(db.Integer,db.ForeignKey('users.id'),index=True)#外键
content = db.Column(db.String(400))#内容
timestamp = db.Column(db.DateTime, index=True,default=datetime.utcnow)
article_id = db.Column(db.Integer,db.ForeignKey('article.id'),index=True)#外键
is_appear = db.Column(db.Boolean()) #是否显示此评论
reply_id = db.Column(db.Integer,index=True) #回复的是哪一条评论
#reply_to_userid = db.Column(db.Integer,index=True)
class User(UserMixin,db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(320), unique=True)
password_hash = db.Column(db.String(128))
user_to_id=db.relationship('Post',foreign_keys=[Post.user_to_id],backref=db.backref('user_to', lazy='joined'),lazy='dynamic',cascade='all, delete-orphan')
posts=db.relationship('Post',foreign_keys=[Post.user_id],backref=db.backref('userid', lazy='joined'),lazy='dynamic',cascade='all, delete-orphan')
address = db.Column(db.String(80))#网址
def __repr__(self):
return '<User{}>'.format(self.username)
def set_password(self,password):
self.password_hash=generate_password_hash(password)
def check_password(self,password):
return check_password_hash(self.password_hash,password)
class Article(db.Model):
__tablename__ = 'article'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(128)) #标题
body=db.Column(db.Text) #原本text格式
body_html = db.Column(db.Text) #转化成html代码后的格式
create_time = db.Column(db.DateTime, index=True, default=datetime.utcnow) #创建时间
seo_link = db.Column(db.String(128)) #文章原链接
pic_path = db.Column(db.String(320)) #封面地址
category_id = db.Column(db.Integer, db.ForeignKey('categories.id')) #文章类别
posts = db.relationship('Post',backref='article',lazy='dynamic') #一个评论的外键
解释一下,根据一中的图我们可以知道post与user为多对多的关系,换句话说,user有两个外键指向post,可是如果设置成两个外键的话post怎么识别对应哪个外键呢?
foreign_keys=[Post.user_to_id]和foreign_keys=[Post.user_id]两个标识就可以解决此问题。
三、命令处理
此处我们默认原本的基本表post,user,article的基本属性已经存在,现在只需要增加上述问题中的几个属性就行了。
一般的先对Post的user表部分修改后,使用下面的命令
flask db migrate -m"posts table"
flask db upgrate
然后修改user
flask db migrate -m"users table"
flask db upgrate
接着对先对Post的article表部分修改后
flask db migrate -m"posts table"
flask db upgrate
最后
flask db migrate -m"article table"
flask db upgrate
请注意以上的顺序,一个不小心就会报错!
四、表单设计
class CommentForm(FlaskForm):
username = StringField('用户名',validators=[DataRequired()])
post=TextAreaField('评论',validators=[DataRequired(),length(min=1,max=200)])
email=StringField ('电子邮箱',validators=[DataRequired(),Email()])
address=StringField('网址或博客网站')
is_appear=BooleanField('私密评论')
submit=SubmitField('提交')
五、路由处理
由于在文章界面增加评论以及评论回复,这里先对文章路由进行修改:
@app.route('/article/<int:id>', methods=['GET', 'POST'])
@csrf.exempt
def article(id):
...
form = CommentForm()
article=Article.query.filter_by(id=id).first_or_404()
posts=Post.query.filter_by(article_id=id).order_by(Post.timestamp.desc())
title=article.title
if request.method == 'POST':
post_username=form.username.data
post_text=form.post.data
post_email=form.email.data
post_address=form.address.data
post_is_appear=form.is_appear.data
user1=User.query.filter_by(username=post_username).first()
user2=User.query.filter_by(email=post_email).first()
if user1 == user2 and user1 is not None and user2 is not None:
post=Post(user_id=user1.id,content=post_text,is_appear=post_is_appear,article_id=article.id,reply_id=0,user_to_id=1)
db.session.add(post)
db.session.commit()
flash('提交成功')
return redirect(url_for('article',id=id))
elif user1 is None and user2 is None:
user=User(username=post_username,email=post_email,address=post_address)
user.set_password("123456")
db.session.add(user)
db.session.commit()
post=Post(user_id=user.id,content=post_text,is_appear=post_is_appear,article_id=article.id,reply_id=0,user_to_id=1)
db.session.add(post)
db.session.commit()
flash('提交成功')
return redirect(url_for('article',id=id))
else:
flash('昵称或者邮箱已经被使用!')
return redirect(url_for('article',id=id))
return render_template('article.html',title=title,article=article,posts=posts,form=form)
这里的思路是,首先用户评论时以昵称创造一个user(相当于注册),需要填写邮箱信息,自己的个人网站,以及评论的内容。此处要判断user昵称是否存在以及邮箱是否存在,因此需要这样的一个代码(上面提到过这里强调一下):
六、增加评论的表单
两个界面,一个是文章界面底下可以进行评论,设置一个form,一个是回复界面。
先是文章底下的form:
<form action="" method="post" class="am-center" role="form" style="width: 90%;">
<div class=" am-u-sm-12 am-u-md-12 am-u-lg-12 am-center">
<div class="am-form-group">
{{ form.username(size=35,class_='am-form-field',placeholder='昵称*') }}
{% for error in form.username.errors %}
<span style="color:red;">[{{error}}]</span>
{%endfor%}
</div>
<div class="am-form-group">
{{ form.email(size=35,class_='am-form-field',type='email',placeholder='电子邮件*') }}
{% for error in form.email.errors %}
<span style="color:red;">[{{error}}]</span>
{%endfor%}
</div>
<div class="am-form-group">
{{ form.address(size=35,class_='am-form-field',type='text',placeholder='你的网址或者博客') }}
{% for error in form.address.errors %}
<span style="color:red;">[{{error}}]</span>
{%endfor%}
</div>
</div>
<br/>
<div class="am-form-group am-u-sm-12 am-u-md-12 am-u-lg-12">
{{ form.post(class_='am-radius',rows='7',id='',style='width:100%;') }}
{% for error in form.post.errors %}
<span style="color:red;">[{{error}}]</span>
{%endfor%}
</div>
<br/>
<div class="am-form-group am-u-sm-12 am-u-md-12 am-u-lg-12 am-text-light">
{{ form.is_appear(class_='am-fr') }}
{{ form.is_appear.label(class_='am-fr light-text-2') }}
{% for error in form.is_appear.errors %}
<span style="color:red;">[{{error}}]</span>
{%endfor%}
</div>
<div class="am-form-group am-u-sm-12 am-u-md-12 am-u-lg-12">
{{ form.submit(class_='am-btn am-btn-default am-fr',type='submit',placeholder='提交') }}
{% for error in form.submit.errors %}
<span style="color:red;">[{{error}}]</span>
{%endfor%}
</div>
</form>
结果为:
然后是点击回复按钮后对应的跳转界面:
七、评论显示
评论显示在文章评论表单的下方,有一下几个注意点:
1.如果评论或者回复为私密,则游客只能看到“此评论为私密,请登录相应账号”的字样(你进行评论或者回复的时候,就相当于注册了一个账号,密码默认为123456,后面会增加功能可供修改),其次,能看到此评论的人有三者:管理员,评论人,评论对象(也可能是管理员)。
2.评论界面中要有层次,评论文章的post在靠近左方的位置,而“回复”内容需要进行缩进。
3.你的post不仅要对应文章、对应对象,还要对应相应的评论id才会在相应的位置底下显示
代码:
<div class="am-u-sm-12 am-u-md-12 am-u-lg-12 am-center">
<div class="light-text am-center" style="width:90%;">
最新评论:
<hr>
</div>
</div>
<div class="am-u-sm-12 am-u-md-12 am-u-lg-12 am-center">
<div class="light-text-2 am-center" style="width:90%;">
{% for post in posts %}
{% if post.reply_id == 0 %}
{% if post.is_appear == True and current_user.username != 'admin' and current_user.id != post.userid.id%}
{{post.timestamp}}
<br>
{{post.userid.username}}评论:<a href="{{url_for('reply',article_id=post.article_id,user_id=post.user_id,user_to_id=post.user_id)}}" class="am-fr">回复</a>
<br>
    此评论为私密内容,需要登录才能看见自己相应的评论
<br>
<hr>
{% else %}
{{post.timestamp}}
<br>
{{post.userid.username}}评论:<a href="{{url_for('reply',article_id=post.article_id,user_id=post.user_id,user_to_id=post.user_id)}}" class="am-fr">回复</a>
<br>
    {{post.content}}
<br>
{% for reply in posts %}
{% if reply.reply_id == post.id %}
{% if reply.is_appear == True and current_user.username != 'admin' and current_user.id != post.userid.id and current_user.id != reply.userid.id and current_user.id != reply.user_to.id %}
<br>
        {{reply.timestamp}}
<br>
        {{reply.userid.username}}评论{{reply.user_to.username}}:<a href="{{url_for('reply',article_id=post.article_id,user_id=post.user_id,user_to_id=reply.user_id)}}" class="am-fr">回复</a>
<br>
            此评论为私密内容,需要登录才能看见自己相应的评论
<br>
{% else %}
<br>
        {{reply.timestamp}}
<br>
        {{reply.userid.username}}评论{{reply.user_to.username}}:<a href="{{url_for('reply',article_id=post.article_id,user_id=post.user_id,user_to_id=reply.user_id)}}" class="am-fr">回复</a>
<br>
            {{post.content}}
<br>
{%endif%}
{%endif%}
{%endfor%}
<hr>
{%endif%}
{%endif%}
{%endfor%}
</div>
</div>
我这里以最简单的表现形式来写的代码可能略微复杂,但是能看懂就好。
其中for循环最外面的posts为路由传过来的一个所有的评论的值,{% if post.reply_id == 0 %}为如果post回复的对象为此文章(作者),则相当于“评论”而不是“评论回复”,不需要进行缩进,否则需要进行缩进。(下面的部分将分为“评论”和“评论回复”两方面讲)
对于一条“评论”来说,私密文章只能是评论的user和文章的作者看到,因此此处做判断 {% if post.is_appear == True and current_user.username != 'admin' and current_user.id != post.userid.id%}判断是否为私密评论。然后显示为“XX评论:XXXX”。
对于一条“评论回复”来说,首先要对应此评论,即{% if reply.reply_id == post.id %},接着判断是否为私密评论,能看到的对象为三方:管理员,评论人,评论对象—— {% if reply.is_appear == True and current_user.username != 'admin' and current_user.id != post.userid.id and current_user.id != reply.userid.id and current_user.id != reply.user_to.id %},最后显示为“XX评论了XX:XXXXX”。
做出的效果为:
(以上如有问题,不吝赐教,真心希望你的批评指正。)