Flask-BBS论坛项目实战3-cms用户名渲染、注销功能实现、模板抽离和后台密码修改

用户名渲染

实现用户名动态展示,其中一种方法就是在视图函数,根据session信息,获取到user id,通过该id找到用户信息,再通过模板变量传递到前端模板。但是这种方法不是很好。因为在其他视图肯定也会用到用户信息,这样的话每个视图函数都要有一个获取用户信息的过程,这样就显得冗余。

之前我们讲过flask中有一个g对象,这个g对象可以在整个flask项目中使用,其实在模板中也可以使用。有了这个g对象,那么我们就可以用户信息存入到这个g对象中,这样可以直接通过这个g对象获取用户信息了。

我可以定义一个before_request钩子函数,在请求视图函数前把用户信息存入g, 编辑cms.views.py

cms.views.py
1
2
3
4
5
6
7
8
9
10
...
from flask import g

@bp.before_request
def before_request():
if config.CMS_USER_ID in session:
user_id = session.get(config.CMS_USER_ID)
user = CMSUser.query.get(user_id)
if user:
g.cms_user = user

这样,我们就可以在前端模板cms_index.html通过g.cms_user.username获取用户名了

1
<li><a href="#">{{ g.cms_user.username }}<span>[超级管理员]</span></a></li>

注销功能实现

注销也比较简单,就是把用户的user id从session中移除就可以了,然后再重定向到登录页面即可

编辑cms.views.py,编写一个logout视图函数

cms.views.py
1
2
3
4
5
@bp.route('/logout/')
@login_required
def logout():
del session[config.CMS_USER_ID]
return redirect(url_for('cms.login'))

修改cms_index.html中注销的链接

1
<li><a href="{{ url_for('cms.logout') }}">注销</a></li>

这样就实现了退出登录的功能了。

代码优化

上面我们把钩子函数写了 cms.views.py文件里面。为了规范一点, views文件我们只写视图,把钩子函数单独写在一个文件里面。
在cms创建一个hooks.py用来专门写钩子函数的,把上面 views里面的钩子函数剪切到cms.hooks.py

cms.hooks.py
1
2
3
4
5
6
7
8
9
10
11
12
from flask import session, g
import config
from .views import bp
from .models import CMSUser

@bp.before_request
def before_request():
if config.CMS_USER_ID in session:
user_id = session.get(config.CMS_USER_ID)
user = CMSUser.query.get(user_id)
if user:
g.cms_user = user

写完cms.hooks.py还不够,因为cms.hooks.py还得不到执行,得不到执行,那么g对象就无法存入用户信息
所以,我们只需要在cms.__init__.py导入它,那么就可以得到执行了

1
2
from .views import bp
import apps.cms.hooks

最后把views.py里的删除就行了

cms模版抽离

新建一个cms_base.html文件作为基础模板,把cms_index.html的内容拷贝到cms_base.html中。
编辑 cms_base.html,把在不同页面会变动的部分用block包起来

标题部分
1
<title>{% block title %}{% endblock %}</title>

在head中再预留一个block个,因为在其他页面可能会再加载一些js或css

1
{% block head %}{% endblock %}

内容标题和内容
1
2
3
4
<h1>{% block page_title %}{% endblock %}</h1>
<div class="main_content">
{% block main_content %}{% endblock %}
</div>

模板已经编辑好了,现在就可以来编辑cms_index.html了
把cms_index.html的原来内容清空,然后继承cms_base.html,在把block填进来填充自己的内容就可以了,如下

1
2
3
4
5
6
7
8
9
10
11
12
{% extends 'cms/cms_base.html' %}

{% block title %}
CMS管理系统
{% endblock %}

{% block page_title %}
欢迎来到CMS管理系统
{% endblock %}


<!--因为首页这里没有其他内容,这里就不配置main_content了-->

个人信息页面

创建个人信息页cms_profile.html,暂时留空即可

编辑cms.views,编写个人信息的视图
cms.views.py

1
2
3
4
@bp.route('/profile/')
@login_required
def profile():
return render_template('cms/cms_profile.html')

再来编辑cms_profile.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
{% extends 'cms/cms_base.html' %}

{% block title %}
个人信息-CMS管理系统
{% endblock %}

{% block page_title %}
个人中心>>>个人信息
{% endblock %}

{% block main_content %}
<!--这里使用的是bootstrap的表格样式,中文网站找到'带边框的表格'-->
<table class="table table-bordered">
<tr>
<td>用户名</td>
<td>{{ g.cms_user.username }}</td>
</tr>
<tr>
<td>邮箱</td>
<td>{{ g.cms_user.email }}</td>
</tr>
<tr>
<td>角色</td>
<td>功能暂未实现</td>
</tr>
<tr>
<td>权限</td>
<td>功能暂未实现</td>
</tr>
<tr>
<td>加入时间</td>
<td>{{ g.cms_user.join_time }}</td>
</tr>


</table>
{% endblock %}

编辑cms_base.html, 把”首页”和”个人信息”的url改过来

1
2
3
<li class="unfold"><a href="{{ url_for('cms.index') }}">首页</a></li>
...
<li><a href="{{ url_for('cms.profile') }}">个人信息</a></li>

大功告成~

cms后台修改密码界面布局

先创建cms_resetpwd.html页面,继承cms_base.html

cms_resetpwd.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
47
48
49
50
{% extends 'cms/cms_base.html' %}

{% block title %}修改密码-CMS管理系统{% endblock %}

{% block page_title %}个人中心>>>修改密码{% endblock %}


{% block head %}

<style>
.form-container{
width: 300px;
}
</style>
{% endblock %}


{% block main_content %}
<form>
<div class="form-container">
<div class="form-group">
<div class="input-group">
<div class="input-group-addon">原密码</div>
<input type="password" class="form-control" placeholder="原密码" name="oldpwd">
</div>

</div>

<div class="form-group">
<div class="input-group">
<div class="input-group-addon">新密码</div>
<input type="password" class="form-control" placeholder="新密码" name="newpwd">
</div>

</div>

<div class="form-group">
<div class="input-group">
<div class="input-group-addon">确认新密码</div>
<input type="password" class="form-control" placeholder="确认新密码" name="newpwd2">
</div>

</div>

<div class="form-group">
<button type="button" class="btn btn-primary" id="submit">立即保存</button>
</div>
</div>
</form>
{% endblock %}

然后编辑cms.views.py,编写修改密码的时候,因为修改密码会用到get和post方法,为了方便,我们就用类视图

cms.views.py
1
2
3
4
5
6
7
8
9
10
11
...

class ResetPwdView(views.MethodView):
decorators = [login_required] #修改密码也要先登录,这是类视图使用装饰器
def get(self):
return render_template('cms/cms_resetpwd.html')

def post(self):
pass

bp.add_url_rule('/resetpwd/', view_func=ResetPwdView.as_view('resetpwd'))

编辑cms_base.html修改 “修改密码” 的url

1
<li><a href="{{ url_for('cms.resetpwd') }}">修改密码</a></li>

cms后台修改密码ajax功能

当我们输入完密码点击”立即保存”,这个表单提交是通过ajax来实现的,因为提交后并不需要跳转到其他页面,这是一个异步的请求。

因为项目中开启了CSRF保护,所以AJAX也要启用csfs,这里为了方便,我们自己封装了一个ajax方法,这个方法其他的地方也可以用,所以把它放在static/common/js/bbsajax.js中(这个js主要是来封装csrf)

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
'use strict';

var bbsajax = {
'get': function (args) {
args['method'] = 'get';
this.ajax(args);
},
'post':function (args) {
args['method'] = 'post';
this.ajax(args);
},
'ajax':function (args) {
//设置csrftoken
this._ajaxSetup();
$.ajax(args);
},
'_ajaxSetup': function () {
$.ajaxSetup({
'beforeSend': function (xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain){
var csrf_token = $('meta[name=csrf-token]').attr('content');
xhr.setRequestHeader("X-CSRFToken", csrf_token)
}
}
});
}
};

把csrf放置cms_base.html的head中,这样其他子模板也会继承了, 把bbsajax.js也引入进来,以便子模板继承

1
2
3
<meta name="csrf-token" content="{{ csrf_token() }}">
..
<script src="{{ url_for('static', filename='common/js/bbsajax.js') }}"></script>

在static/cms/js/新建一个resetpwd.js

resetpwd.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
/**
* Created by user on 2018/8/7.
*/

$(function () {
$('#submit').click(function (event) {
//阻止按钮默认的提交表单行为
event.preventDefault();
var oldpwdE = $('input[name=oldpwd]');
var newpwdE = $('input[name=newpwd]');
var newpwd2E = $('input[name=newpwd2]');

var oldpwd = oldpwdE.val();
var newpwd = newpwdE.val();
var newpwd2 = newpwd2E.val();

//这里使用我们自己封装好的bbsajax,它具有了csrf
bbsajax.post({
'url': '/cms/resetpwd/',
'data': {
'oldpwd': oldpwd,
'newpwd': newpwd,
'newpwd2': newpwd2
},
'success': function (data) {
console.log(data);
},
'fail': function (error) {
console.log(error);
}
});
});
})

在cms_resetpwd.html中,把resetpwd.js文件引入进来

1
2
3
4
{% block head %}
...
<script src="{{ url_for('static', filename='cms/js/resetpwd.js') }}"></script>
{% endblock %}

cms后台修改密码功能

现在可以写后端修改密码的逻辑了,用户提交表单上来,我们就要先对表单进行验证,因此先编辑cms.forms.py写一个RestPwdForm

cms.forms.py
1
2
3
4
5
6
7
...
from wtforms.validators import Length, EqualTo

class ResetPwdForm(BaseForm):
oldpwd = StringField(validators=[Length(6,30, message='密码长度6-30')])
newpwd = StringField(validators=[Length(6,30, message='密码长度6-30')])
newpwd2 = StringField(validators=[EqualTo('newpwd', message='新密码输入不一致')])

编辑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
from flask import jsonify, g
from exts import db
from .forms import ResetPwdForm
...

class ResetPwdView(views.MethodView):
decorators = [login_required]
def get(self):
return render_template('cms/cms_resetpwd.html')

def post(self):
resetpwd_form = ResetPwdForm(request.form)
if resetpwd_form.validate():
oldpwd = resetpwd_form.oldpwd.data
newpwd = resetpwd_form.newpwd.data
user = g.cms_user
if user.check_password(oldpwd):
user.password = newpwd
db.session.commit()
#因为接受的是ajax,所以这里使用jsonify返回数据
#返回code字段表示状态码,message信息提示
return jsonify({"code": 200, "message":"修改成功"})
else:
return jsonify({"code": 400, "message": "原密码错误"})
else:
message = resetpwd_form.get_error()
return jsonify({"code": 400, "message":message})

然后我们测试一下

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