Flask-BBS论坛项目实战9-发布帖子功能完成和帖子过滤及加精功能

发布帖子完成

首先给帖子设计个模型,编辑apps.models.py

1
2
3
4
5
6
7
8
9
10
11
class PostModel(db.Model):
__tablename__ = 'post'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text, nullable=False)
create_time = db.Column(db.DateTime, default=datetime.now)
board_id = db.Column(db.Integer, db.ForeignKey('board.id'))
author_id = db.Column(db.String(100), db.ForeignKey('front_user.id'), nullable=False)

board = db.relationship("BoardModel", backref="posts")
author = db.relationship("FrontUser", backref='posts')

同步到数据库

发表帖子是需要前台用户登录才可以的,因此,我先完成下验证前台用户是否登录的装饰器,创建front.decorators.py

1
2
3
4
5
6
7
8
9
10
11
12
from functools import wraps
from flask import session, redirect, url_for
import config

def login_required(func):
@wraps(func)
def inner(*args, **kwargs):
if config.FRONT_USER_ID in session:
return func(*args, **kwargs)
else:
return redirect(url_for('front.signin'))
return inner

写一个钩子函数,用来全局使用登录用户的信息,创建front.hooks.py

1
2
3
4
5
6
7
8
9
10
11
12
13
from .views import bp
import config
from flask import session,g,render_template
from .models import FrontUser


@bp.before_request
def my_before_request():
if config.FRONT_USER_ID in session:
user_id = session.get(config.FRONT_USER_ID)
user = FrontUser.query.get(user_id)
if user:
g.front_user = user

编辑front.init.py

1
2
from .views import bp
from . import hooks

编辑front.views.py,发布帖子的视图函数,首先写个form验证

1
2
3
4
class AddPostForm(BaseForm):
title = StringField(validators=[InputRequired(message='请输入标题!')])
content = StringField(validators=[InputRequired(message='请输入内容!')])
board_id = IntegerField(validators=[InputRequired(message='请输入板块id!')])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@bp.route('/apost/', methods=['GET', 'POST'])
@login_required
def apost():
if request.method == 'GET':
boards = BoardModel.query.all()
return render_template('front/front_apost.html', boards=boards)
else:
add_post_form = AddPostForm(request.form)
if add_post_form.validate():
title = add_post_form.title.data
content = add_post_form.content.data
board_id = add_post_form.board_id.data
board = BoardModel.query.get(board_id)
if not board:
return xjson.json_param_error(message='没有这个板块')
post = PostModel(title=title, content=content)
post.board = board
post.author = g.front_user
db.session.add(post)
db.session.commit()
return xjson.json_success()
else:
return xjson.json_param_error(message=add_post_form.get_error())

配置UEditor富文本编辑器
在apps下新建一个python packge命名为ueditor, 在uedittor下新建ueditor.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
from flask import (
Blueprint,
request,
jsonify,
url_for,
send_from_directory,
current_app as app
)
import json
import re
import string
import time
import hashlib
import random
import base64
import sys
import os
from urllib import parse
# 更改工作目录。这么做的目的是七牛qiniu的sdk
# 在设置缓存路径的时候默认会设置到C:/Windows/System32下面
# 会造成没有权限创建。
os.chdir(os.path.abspath(sys.path[0]))
try:
import qiniu
except:
pass
from io import BytesIO

bp = Blueprint('ueditor',__name__,url_prefix='/ueditor')

UEDITOR_UPLOAD_PATH = ""
UEDITOR_UPLOAD_TO_QINIU = False
UEDITOR_QINIU_ACCESS_KEY = ""
UEDITOR_QINIU_SECRET_KEY = ""
UEDITOR_QINIU_BUCKET_NAME = ""
UEDITOR_QINIU_DOMAIN = ""

@bp.before_app_first_request
def before_first_request():
global UEDITOR_UPLOAD_PATH
global UEDITOR_UPLOAD_TO_QINIU
global UEDITOR_QINIU_ACCESS_KEY
global UEDITOR_QINIU_SECRET_KEY
global UEDITOR_QINIU_BUCKET_NAME
global UEDITOR_QINIU_DOMAIN
UEDITOR_UPLOAD_PATH = app.config.get('UEDITOR_UPLOAD_PATH')
if UEDITOR_UPLOAD_PATH and not os.path.exists(UEDITOR_UPLOAD_PATH):
os.mkdir(UEDITOR_UPLOAD_PATH)

UEDITOR_UPLOAD_TO_QINIU = app.config.get("UEDITOR_UPLOAD_TO_QINIU")
if UEDITOR_UPLOAD_TO_QINIU:
try:
UEDITOR_QINIU_ACCESS_KEY = app.config["UEDITOR_QINIU_ACCESS_KEY"]
UEDITOR_QINIU_SECRET_KEY = app.config["UEDITOR_QINIU_SECRET_KEY"]
UEDITOR_QINIU_BUCKET_NAME = app.config["UEDITOR_QINIU_BUCKET_NAME"]
UEDITOR_QINIU_DOMAIN = app.config["UEDITOR_QINIU_DOMAIN"]
except Exception as e:
option = e.args[0]
raise RuntimeError('请在app.config中配置%s!'%option)

csrf = app.extensions.get('csrf')
if csrf:
csrf.exempt(upload)


def _random_filename(rawfilename):
letters = string.ascii_letters
random_filename = str(time.time()) + "".join(random.sample(letters,5))
filename = hashlib.md5(random_filename.encode('utf-8')).hexdigest()
subffix = os.path.splitext(rawfilename)[-1]
return filename + subffix


@bp.route('/upload/',methods=['GET','POST'])
def upload():
action = request.args.get('action')
result = {}
if action == 'config':
config_path = os.path.join(bp.static_folder or app.static_folder,'ueditor','config.json')
with open(config_path,'r',encoding='utf-8') as fp:
result = json.loads(re.sub(r'\/\*.*\*\/','',fp.read()))

elif action in ['uploadimage','uploadvideo','uploadfile']:
image = request.files.get("upfile")
filename = image.filename
save_filename = _random_filename(filename)
result = {
'state': '',
'url': '',
'title': '',
'original': ''
}
if UEDITOR_UPLOAD_TO_QINIU:
if not sys.modules.get('qiniu'):
raise RuntimeError('没有导入qiniu模块!')
buffer = BytesIO()
image.save(buffer)
buffer.seek(0)
q = qiniu.Auth(UEDITOR_QINIU_ACCESS_KEY, UEDITOR_QINIU_SECRET_KEY)
token = q.upload_token(UEDITOR_QINIU_BUCKET_NAME)
ret,info = qiniu.put_data(token,save_filename,buffer.read())
if info.ok:
result['state'] = "SUCCESS"
result['url'] = parse.urljoin(UEDITOR_QINIU_DOMAIN,ret['key'])
result['title'] = ret['key']
result['original'] = ret['key']
else:
image.save(os.path.join(UEDITOR_UPLOAD_PATH, save_filename))
result['state'] = "SUCCESS"
result['url'] = url_for('ueditor.files',filename=save_filename)
result['title'] = save_filename,
result['original'] = image.filename

elif action == 'uploadscrawl':
base64data = request.form.get("upfile")
img = base64.b64decode(base64data)
filename = _random_filename('xx.png')
filepath = os.path.join(UEDITOR_UPLOAD_PATH,filename)
with open(filepath,'wb') as fp:
fp.write(img)
result = {
"state": "SUCCESS",
"url": url_for('files',filename=filename),
"title": filename,
"original": filename
}
return jsonify(result)


@bp.route('/files/<filename>/')
def files(filename):
return send_from_directory(UEDITOR_UPLOAD_PATH,filename)

编辑ueditor.init.py

1
from .ueditor import bp

在主程序中注册蓝图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from flask import Flask
import config
from apps.cms import bp as cms_bp
from apps.common import bp as common_bp
from apps.front import bp as front_bp
from exts import db, mail
from flask_wtf import CSRFProtect
from apps.ueditor import bp as ueditor_bp


def create_app():
app = Flask(__name__)
app.config.from_object(config)

app.register_blueprint(cms_bp)
app.register_blueprint(front_bp)
app.register_blueprint(common_bp)
app.register_blueprint(ueditor_bp)
db.init_app(app)
CSRFProtect(app)
mail.init_app(app)

return app


if __name__ == '__main__':
app = create_app()
app.run()

前台配置

在templates/front下创建front_apost.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{% extends "front/front_base.html" %}

{% block title %}
发布帖子
{% endblock %}

{% block head %}
<script src="{{ url_for('static',filename='ueditor/ueditor.config.js') }}"></script>
<script src="{{ url_for('static',filename='ueditor/ueditor.all.min.js') }}"></script>
{% endblock %}

{% block body %}
<form action="" method="post">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon">标题</span>
<input type="text" class="form-control" name="title">
</div>
</div>
<div class="form-group">
<div class="input-group">
<span class="input-group-addon">板块</span>
<select name="board_id" class="form-control">
{% for board in boards %}
<option value="{{ board.id }}">{{ board.name }}</option>
{% endfor %}

</select>
</div>
</div>
<div class="form-group">
<script id="editor" type="text/plain" style="height:500px;"></script>
</div>
<div class="form-group">
<button class="btn btn-danger" id="submit-btn">发布帖子</button>
</div>
</form>
{% endblock %}

在static/front/js下创建front_apost.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* Created by Administrator on 2018/10/6.
*/

/**
* Created by hynev on 2017/12/31.
*/

$(function () {
var ue = UE.getEditor("editor",{
"serverUrl": '/ueditor/upload/'
});

$("#submit-btn").click(function (event) {
event.preventDefault();
var titleInput = $('input[name="title"]');
var boardSelect = $("select[name='board_id']");

var title = titleInput.val();
var board_id = boardSelect.val();
var content = ue.getContent();

bbsajax.post({
'url': '/apost/',
'data': {
'title': title,
'content':content,
'board_id': board_id
},
'success': function (data) {
if(data['code'] == 200){
xtalert.alertConfirm({
'msg': '恭喜!帖子发表成功!',
'cancelText': '回到首页',
'confirmText': '再发一篇',
'cancelCallback': function () {
window.location = '/';
},
'confirmCallback': function () {
titleInput.val("");
ue.setContent("");
}
});
}else{
xtalert.alertInfo(data['message']);
}
}
});
});
});

然后在front_apost.html中引入front_apost.js

编辑front_index.html

1
<a href="{{ url_for("front.apost") }}" class="btn btn-warning btn-block">发布帖子</a>

首页帖子布局完成

编辑front_index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<div id="carousel-example-generic" class="carousel slide index-banner" data-ride="carousel">
...
<div class="post-group">
<ul class="post-group-head">
<li class="active"><a href="{{ url_for("front.index",st=1,bd=current_board) }}">最新</a></li>
<li><a href="{{ url_for("front.index",st=2,bd=current_board) }}">精华帖子</a></li>
<li><a href="{{ url_for("front.index",st=3,bd=current_board) }}">点赞最多</a></li>
<li><a href="{{ url_for("front.index",st=4,bd=current_board) }}">评论最多</a></li>
</ul>
<ul class="post-list-group">
{% for post in posts %}
<li>
<div class="author-avatar-group">
<img src="{{ post.author.avatar or url_for('static',filename='common/images/logo.png') }}" alt="">
</div>
<div class="post-info-group">
<p class="post-title">
<a href="#">{{ post.title }}</a>
{% if post.highlight %}
<span class="label label-danger">精华帖</span>
{% endif %}
</p>
<p class="post-info">
<span>作者:{{ post.author.username }}</span>
<span>发表时间:{{ post.create_time }}</span>
<span>评论:0</span>
<span>阅读:0</span>
</p>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>

编辑front_index.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
.index-banner{
border-radius: 10px;
overflow: hidden;
height: 200px;
}

/*需要把图片的高度和轮播的一致*/
.index-banner img{
height: 200px;
}

.post-group{
border: 1px solid #ddd;
margin-top: 20px;
overflow: hidden;
border-radius: 5px;
padding: 10px;
}

.post-group-head{
overflow: hidden;
list-style: none;
}

.post-group-head li{
float: left;
padding: 5px 10px;
}

.post-group-head li a{
color:#333;
}

.post-group-head li.active{
background: #ccc;
}

.post-list-group{
margin-top: 20px;
}

.post-list-group li{
overflow: hidden;
padding-bottom: 20px;
}

.author-avatar-group{
float: left;
}

.author-avatar-group img{
width: 50px;
height: 50px;
border-radius: 50%;
}

.post-info-group{
float: left;
margin-left: 10px;
border-bottom: 1px solid #e6e6e6;
width: 85%;
padding-bottom: 10px;
}

.post-info-group .post-info{
margin-top: 10px;
font-size: 12px;
color: #8c8c8c;
}

.post-info span{
margin-right: 10px;
}


编辑front_base.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

<ul class="nav navbar-nav navbar-right">
{% if g.front_user %}
<span id="login-tag" data-is-login="1" style="display:none;"></span>
<li class="dropdown">
<a href="#" class="dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
{{ g.front_user.username }}
<span class="caret"></span>
</a>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a href="#">个人中心</a></li>
<li><a href="#">设置</a></li>
<li><a href="#">注销</a></li>
</ul>
</li>
{% else %}
<li><a href="{{ url_for('front.signin') }}">登录</a></li>
<li><a href="{{ url_for("front.signup") }}">注册</a></li>
{% endif %}
</ul>

帖子分页技术实现

编辑manage.py,添加测试帖子

1
2
3
4
5
6
7
8
9
10
11
12
13
@manager.command
def create_test_post():
for x in range(1, 100):
title = '标题{}'.format(x)
content = '内容:{}'.format(x)
board = BoardModel.query.first()
author = FrontUser.query.first()
post = PostModel(title=title, content=content)
post.board = board
post.author = author
db.session.add(post)
db.session.commit()
print('测试帖子添加成功')

运行

1
python manage.py create_test_post

在 flask框架中,我们可以使用Flask Paginate插件来实现分页

安装插件

1
pip install flask-paginate

编辑配config.py,配置每页显示的帖子数

1
2
#flask-paginate的相关配置
PER_PAGE = 6 #每页显示6篇帖子

编辑首页的视图函数,编辑front.views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from flask_paginate import Pagination, get_page_parameter

...
@bp.route('/')
def index():
banners = BannerModel.query.order_by(BannerModel.priority.desc()).all()
boards = BoardModel.query.all()


# 当前页面
page = request.args.get(get_page_parameter(), type=int, default=1)
# 开始位置
start = (page - 1) * config.PER_PAGE
# 结束位置
end = start + config.PER_PAGE
posts = PostModel.query.slice(start, end)

pagination = Pagination(bs_version=3,page=page, total=PostModel.query.count())

context = {
'banners': banners,
'boards': boards,
'posts':posts,
'pagination':pagination,
}
return render_template('front/front_index.html', **context)

现在刷新首页只会显示6篇帖子了

实现翻页

编辑front_index.html,在帖子下面加上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    <ul class="post-list-group">
{% for post in posts %}
<li>
<div class="author-avatar-group">
<img src="{{ post.author.avatar or url_for('static',filename='common/images/logo.png') }}" alt="">
</div>
<div class="post-info-group">
<p class="post-title">
<a href="#">{{ post.title }}</a>
{% if post.highlight %}
<span class="label label-danger">精华帖</span>
{% endif %}
</p>
<p class="post-info">
<span>作者:{{ post.author.username }}</span>
<span>发表时间:{{ post.create_time }}</span>
<span>评论:0</span>
<span>阅读:0</span>
</p>
</div>
</li>
{% endfor %}
</ul>

<div style="text-align: center">
{{ pagination.links }}
</div>

刷新页面即可。

帖子板块过滤显示

先在显示的帖子是所有版块的帖子,这节我们来完成点击某个版块,则显示此版块的帖子

要完成这个功能,我们需要在前端传递板块的id到后台, 编辑front_index.html

1
2
3
4
5
6
7
8
9
10
11
<div class="sm-container">
<div style="padding-bottom: 10px">
<a href="{{ url_for("front.apost") }}" class="btn btn-warning btn-block">发布帖子</a>
</div>

<div class="list-group">
<a href="/" class="list-group-item active">所有板块</a>
{% for board in boards %}
<a href="{{ url_for('front.index', bd = board.id) }}" class="list-group-item ">{{ board.name }}</a>
{% endfor %}
</div>

编辑首页视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@bp.route('/')
def index():
banners = BannerModel.query.order_by(BannerModel.priority.desc()).all()
boards = BoardModel.query.all()


# 当前页面
page = request.args.get(get_page_parameter(), type=int, default=1)
# 开始位置
start = (page - 1) * config.PER_PAGE
# 结束位置
end = start + config.PER_PAGE

board_id = request.args.get('bd', type=int, default=None)
if board_id:
query_obj = PostModel.query.filter_by(board_id = board_id)
posts = query_obj.slice(start, end)
total = query_obj.count()

else:
posts = PostModel.query.slice(start, end)
total = PostModel.query.count()


pagination = Pagination(bs_version=3,page=page, total=total)

context = {
'banners': banners,
'boards': boards,
'posts':posts,
'pagination':pagination,
'current_board':board_id
}
return render_template('front/front_index.html', **context)

编辑板块选中样式

1
2
3
4
5
6
7
8
9
10
11
12
   <div class="sm-container">
<div style="padding-bottom: 10px">
<a href="{{ url_for("front.apost") }}" class="btn btn-warning btn-block">发布帖子</a>
</div>

<div class="list-group">
<a href="/" class="list-group-item {% if not current_board %}active{% endif %}">所有板块</a>
{% for board in boards %}
<a href="{{ url_for('front.index', bd = board.id) }}" class="list-group-item {% if current_board == board.id %}active{% endif %}">{{ board.name }}</a>
{% endfor %}
</div>
</div>

帖子详情页布局

在templates/front/下创建详情页面front_pdetail.html

编辑front.views.py创建详情页的视图函数

1
2
3
4
5
6
7
8
9
from flask import abort
...

@bp.route('/p/<post_id>/')
def post_detail(post_id):
post = PostModel.query.get(post_id)
if not post:
abort(404)
return render_template('front/front_pdetail.html', post=post)

上面写了,如果帖子不存在,则返回404,我们先创建个404页面

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>BBS论坛404页面</title>
</head>
<body>
您要找的页面已经到火星去了~~
<div>
<a href="/">回到首页</a>
</div>
</body>
</html>

然后我们写个钩子函数,返回404的页面, 编辑front.hooks.py

1
2
3
@bp.errorhandler
def page_not_found():
return render_template('front/front_404.html'),404

帖子详情页面布局

编辑front_pdetail.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{% extends 'front/front_base.html' %}

{% block title %}
{{ post.title }}
{% endblock %}


{% block head %}
<link rel="stylesheet" href="{{ url_for('static', filename='front/css/front_pdetail.css') }}">
{% endblock %}

{% block body %}
<div class="lg-container">
<div class="post-container">
<h2>{{ post.title }}</h2>
<p class="post-info-group">
<span>发表时间:{{ post.create_time }}</span>
<span>作者:{{ post.author.username }}</span>
<span>所属板块:{{ post.board.name }}</span>
<span>阅读数:{{ post.read_count }}</span>
<span>评论数:0</span>
</p>
<article class="post-content" id="post-content" data-id="{{ post.id }}">
{{ post.content|safe }}
</article>
</div>

</div>
<div class="sm-container"></div>
{% endblock %}

front_pdetail.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.post-container{
border: 1px solid #e6e6e6;
padding: 10px;
}

.post-info-group{
font-size: 12px;
color: #8c8c8c;
border-bottom: 1px solid #e6e6e6;
margin-top: 20px;
padding-bottom: 10px;
}

.post-info-group span{
margin-right: 20px;
}

.post-content{
margin-top: 20px;
}

.post-content img{
max-width: 100%;
}

在首页配置帖子的链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="post-info-group">
<p class="post-title">
<a href="{{ url_for('front.post_detail', post_id=post.id) }}">{{ post.title }}</a>
{% if post.highlight %}
<span class="label label-danger">精华帖</span>
{% endif %}
</p>
<p class="post-info">
<span>作者:{{ post.author.username }}</span>
<span>发表时间:{{ post.create_time }}</span>
<span>评论:0</span>
<span>阅读:0</span>
</p>
</div>

评论布局和功能实现

评论后端逻辑实现

设计评论模型表, 编辑apps.models.py

1
2
3
4
5
6
7
8
9
10
class CommentModel(db.Model):
__tablename__ = 'comment'
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
content = db.Column(db.Text,nullable=False)
create_time = db.Column(db.DateTime,default=datetime.now)
post_id = db.Column(db.Integer,db.ForeignKey("post.id"))
author_id = db.Column(db.String(100), db.ForeignKey("front_user.id"), nullable=False)

post = db.relationship("PostModel",backref='comments')
author = db.relationship("FrontUser",backref='comments')

然后同步表到数据库
后端需要对评论进行表单验证,编辑front.forms.py

1
2
3
class AddCommentForm(BaseForm):
content = StringField(validators=[InputRequired(message='请输入评论内容!')])
post_id = IntegerField(validators=[InputRequired(message='请输入帖子id!')])

写视图函数,编辑front.views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from .forms import AddCommentForm
from apps.models import CommentModel
...

@bp.route('/acomment/',methods=['POST'])
@login_required
def add_comment():
add_comment_form = AddCommentForm(request.form)
if add_comment_form.validate():
content = add_comment_form.content.data
post_id = add_comment_form.post_id.data
post = PostModel.query.get(post_id)
if post:
comment = CommentModel(content=content)
comment.post = post
comment.author = g.front_user
db.session.add(comment)
db.session.commit()
return xjson.json_success()
else:
return xjson.json_param_error('没有这篇帖子!')
else:
return xjson.json_param_error(add_comment_form.get_error())

评论前端布局

front_pdetail.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<div class="lg-container">
...
<div class="comment-group">
<h3>评论列表</h3>
<ul class="comment-list-group">
{% for comment in post.comments %}
<li>
<div class="avatar-group">
<img src="{{ comment.author.avatar or url_for('static', filename='common/images/logo.png') }}" alt="">
</div>
<div class="comment-content">
<p class="author-info">
<span>{{ comment.author.username }}</span>
<span>{{ comment.create_time }}</span>
</p>
<p class="comment-txt">
{{ comment.content|safe }}
</p>
</div>
</li>
{% endfor %}
</ul>
</div>
<div class="add-comment-group">
<h3>发表评论</h3>
<script id="editor" type="text/plain" style="height:100px;"></script>
<div class="comment-btn-group">
<button class="btn btn-primary" id="comment-btn">发表评论</button>
</div>
</div>
</div>

评论需要用到ueditor编辑器,因此也要引入以下js

1
2
3
4
5
{% block head %}
<script src="{{ url_for('static',filename='ueditor/ueditor.config.js') }}"></script>
<script src="{{ url_for('static',filename='ueditor/ueditor.all.min.js') }}"></script>
...
{% endblock %}

编辑front_pdetail.css,设置样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
...
.comment-group{
margin-top: 20px;
border: 1px solid #e8e8e8;
padding: 10px;
}

.add-comment-group{
margin-top: 20px;
padding: 10px;
border: 1px solid #e8e8e8;
}

.add-comment-group h3{
margin-bottom: 10px;
}

.comment-btn-group{
margin-top: 10px;
text-align:right;
}

.comment-list-group li{
overflow: hidden;
padding: 10px 0;
border-bottom: 1px solid #e8e8e8;
}

.avatar-group{
float: left;
}

.avatar-group img{
width: 50px;
height: 50px;
border-radius: 50%;
}

.comment-content{
float: left;
margin-left:10px;
}

.comment-content .author-info{
font-size: 12px;
color: #8c8c8c;
}

.author-info span{
margin-right: 10px;
}

.comment-content .comment-txt{
margin-top: 10px;
}

最后,创建front_pdetail.js并引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
$(function () {
var ue = UE.getEditor("editor",{
'serverUrl': '/ueditor/upload/',
//因为是评论,富文本比编辑器不需要那么多功能,所以这里只列出要用的
//一个列表代表一行
"toolbars": [
[
'undo', //撤销
'redo', //重做
'bold', //加粗
'italic', //斜体
'source', //源代码
'blockquote', //引用
'selectall', //全选
'insertcode', //代码语言
'fontfamily', //字体
'fontsize', //字号
'simpleupload', //单图上传
'emotion' //表情
]
]
});
//把ue设置为全局
window.ue = ue;
});

$(function () {
$("#comment-btn").click(function (event) {
event.preventDefault();
var loginTag = $("#login-tag").attr("data-is-login");
if(!loginTag){
window.location = '/signin/';
}else{
var content = window.ue.getContent();
var post_id = $("#post-content").attr("data-id");
bbsajax.post({
'url': '/acomment/',
'data':{
'content': content,
'post_id': post_id
},
'success': function (data) {
if(data['code'] == 200){
window.location.reload();
}else{
xtalert.alertInfo(data['message']);
}
}
});
}
});
});

帖子加精和取消加精功能完成

帖子加精和取消加精是在cms后台来设置的

后台逻辑

首页个帖子加精设计个模型表,编辑apps.models.py

1
2
3
4
5
6
7
class HighlightPostModel(db.Model):
__tablename__ = 'highlight_post'
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
post_id = db.Column(db.Integer,db.ForeignKey("post.id"))
create_time = db.Column(db.DateTime,default=datetime.now)

post = db.relationship("PostModel",backref="highlight")

然后将数据同步表到数据库

视图函数,编辑cms.views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from apps.models import HighlightPostModel, PostModel
...

@bp.route('/posts/')
@login_required
@permission_required(CMSPersmission.POSTER)
def posts():
post_list = PostModel.query.all()
return render_template('cms/cms_posts.html', posts=post_list)


@bp.route('/hpost/',methods=['POST'])
@login_required
@permission_required(CMSPersmission.POSTER)
def hpost():
post_id = request.form.get("post_id")
if not post_id:
return xjson.json_param_error('请传入帖子id!')
post = PostModel.query.get(post_id)
if not post:
return xjson.json_param_error("没有这篇帖子!")

highlight = HighlightPostModel()
highlight.post = post
db.session.add(highlight)
db.session.commit()
return xjson.json_success()


@bp.route('/uhpost/',methods=['POST'])
@login_required
@permission_required(CMSPersmission.POSTER)
def uhpost():
post_id = request.form.get("post_id")
if not post_id:
return xjson.json_param_error('请传入帖子id!')
post = PostModel.query.get(post_id)
if not post:
return xjson.json_param_error("没有这篇帖子!")

highlight = HighlightPostModel.query.filter_by(post_id=post_id).first()
db.session.delete(highlight)
db.session.commit()
return xjson.json_success()

前端逻辑完成

cms_posts.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{% extends 'cms/cms_base.html' %}

{% block title %}
帖子管理-CMS管理系统
{% endblock %}

{% block page_title %}
帖子管理
{% endblock %}

{% block head %}
<script src="{{ url_for('static', filename='cms/js/posts.js')}}"></script>
{% endblock %}

{% block main_content %}
<table class="table table-bordered">
<thead>
<tr>
<th>标题</th>
<th>发布时间</th>
<th>板块</th>
<th>作者</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for post in posts %}
<tr data-id="{{ post.id }}" data-highlight="{{ 1 if post.highlight else 0 }}">
<td><a target="_blank" href="{{ url_for("front.post_detail",post_id=post.id) }}">{{ post.title }}</a></td>
<td>{{ post.create_time }}</td>
<td>{{ post.board.name }}</td>
<td>{{ post.author.username }}</td>
<td>
{% if post.highlight %}
<button class="btn btn-default btn-xs highlight-btn">取消加精</button>
{% else %}
<button class="btn btn-default btn-xs highlight-btn">加精</button>
{% endif %}
<button class="btn btn-danger btn-xs">移除</button>
</td>
</tr>
{% endfor %}

</tbody>
</table>
{% endblock %}

posts.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$(function () {
$(".highlight-btn").click(function () {
var self = $(this);
var tr = self.parent().parent();
var post_id = tr.attr("data-id");
var highlight = parseInt(tr.attr("data-highlight"));
var url = "";
if(highlight){
url = "/cms/uhpost/";
}else{
url = "/cms/hpost/";
}
bbsajax.post({
'url': url,
'data': {
'post_id': post_id
},
'success': function (data) {
if(data['code'] == 200){
xtalert.alertSuccessToast('操作成功!');
setTimeout(function () {
window.location.reload();
},500);
}else{
xtalert.alertInfo(data['message']);
}
}
});
});
});

帖子按照发布时间和评论数量等排序


排序,我们需要在前端传递参数, 编辑front_index.html

1
2
3
4
5
6
<ul class="post-group-head">
<li class="{% if current_sort==1 %}active{% endif %}"><a href="{{ url_for("front.index",st=1,bd=current_board) }}">最新</a></li>
<li class="{% if current_sort==2 %}active{% endif %}"><a href="{{ url_for("front.index",st=2,bd=current_board) }}">精华帖子</a></li>
<li class="{% if current_sort==3 %}active{% endif %}"><a href="{{ url_for("front.index",st=3,bd=current_board) }}">点赞最多</a></li>
<li class="{% if current_sort==4 %}active{% endif %}"><a href="{{ url_for("front.index",st=4,bd=current_board) }}">评论最多</a></li>
</ul>

编辑front.views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from apps.models import HighlightPostModel
from sqlalchemy.sql import func
...


@bp.route('/')
def index():
banners = BannerModel.query.order_by(BannerModel.priority.desc()).all()
boards = BoardModel.query.all()

# 当前页面
page = request.args.get(get_page_parameter(), type=int, default=1)
# 开始位置
start = (page - 1) * config.PER_PAGE
# 结束位置
end = start + config.PER_PAGE

board_id = request.args.get('bd',type=int, default=None)
sort = request.args.get("st", type=int, default=1)
query_obj = None
if sort == 1:
query_obj = PostModel.query.order_by(PostModel.create_time.desc())
elif sort == 2:
# 按照加精的时间倒叙排序
query_obj = db.session.query(PostModel).outerjoin(HighlightPostModel).order_by(
HighlightPostModel.create_time.desc(), PostModel.create_time.desc())
elif sort == 3:
# 按照点赞的数量排序,点赞功能没有做,所以这里用时间倒序排序
query_obj = PostModel.query.order_by(PostModel.create_time.desc())
elif sort == 4:
# 按照评论的数量排序
query_obj = db.session.query(PostModel).outerjoin(CommentModel).group_by(PostModel.id).order_by(
func.count(CommentModel.id).desc(), PostModel.create_time.desc())

if board_id:
query_obj = query_obj.filter(PostModel.board_id == board_id)
posts = query_obj.slice(start, end)
total = query_obj.count()
else:
posts = query_obj.slice(start, end)
total = query_obj.count()

pagination = Pagination(bs_version=3,page=page, total=total)
context = {
'banners': banners,
'boards': boards,
'posts': posts,
'pagination': pagination,
'current_board': board_id,
'current_sort': sort
}
return render_template('front/front_index.html', **context)

排序 功能已经完成了

-------------本文结束感谢您的阅读-------------