文章目录
- 一、CMS用户登录功能
- 二、CMS用户错误信息返回
- 三、CMS用户登录限制和CSRF保护
- 1.登录验证
- 2.CSRF保护
- 四、CMS用户名渲染和注销功能
- 1.后台页面基本实现
- 2.用户名渲染
- 3.注销功能实现
- 五、CMS个人页面和模板抽离
- 1.个人信息页面实现
- 2.模板抽离
´´´´´████████´´´´
´´`´███▒▒▒▒███´´´´´
´´´███▒●▒▒●▒██´´´
´´´███▒▒▒▒▒▒██´´´´´
´´´███▒▒▒▒██´
´´██████▒▒███´´´´´
´██████▒▒▒▒███´´
██████▒▒▒▒▒▒███´´´´
´´▓▓▓▓▓▓▓▓▓▓▓▓▓▒´´
´´▒▒▒▒▓▓▓▓▓▓▓▓▓▒´´´´´
´.▒▒▒´´▓▓▓▓▓▓▓▓▒´´´´´
´.▒▒´´´´▓▓▓▓▓▓▓▒
…▒▒.´´´´▓▓▓▓▓▓▓▒
´▒▒▒▒▒▒▒▒▒▒▒▒
´´´´´´´´´███████´´´´
´´´´´´´´████████´´´´´´
´´´´´´´█████████´´´´´
´´´´´´██████████´´´
´´´´´´██████████´´
´´´´´´´█████████´
´´´´´´´█████████´
´´´´´´´´████████´´´
________▒▒▒▒▒
_________▒▒▒▒
_________▒▒▒▒
________▒▒_▒▒
_______▒▒__▒▒
_____ ▒▒___▒▒
_____▒▒___▒▒
___▒▒____▒▒
▒▒_____▒▒
███ ▒▒
████____███
█ ███ _█_███
520已经过去3天了,送大家一个女朋友吧,自提!!!
Github和Gitee代码同步更新:
https://github.com/PythonFullStack/Flask_BBS;
https://gitee.com/Python_Full_Stack/Flask_BBS。
一、CMS用户登录功能
上一节已经完成了后台管理员登录界面的渲染,现在进一步实现用户登录功能。
先实现表单验证,cms目录下的forms.py如下:
from wtforms import Form, StringField, IntegerField
from wtforms.validators import Email, InputRequired, Length
class LoginForm(Form):
email = StringField(validators=[Email(message='请输入正确的邮箱地址'), InputRequired(message='请输入邮箱')])
password = StringField(validators=[Length(6, 20, message='密码长度应介于6-20')])
remember = IntegerField()
views.py增加post函数如下:
from flask import Blueprint, render_template, views, request, redirect, url_for
from apps.cms.forms import LoginForm
cms_bp = Blueprint('cms', __name__, url_prefix='/cms')
@cms_bp.route('/')
def index():
return '后台管理首页'
class LoginView(views.MethodView):
def get(self):
return render_template('cms/cms_login.html')
def post(self):
login_form = LoginForm(request.form)
if login_form.validate():
return redirect(url_for('cms.index'))
else:
print(login_form.errors)
return '邮箱或密码有误'
cms_bp.add_url_rule('/login/', view_func=LoginView.as_view('login'))
命令行增加管理员用户:
python manage.py create_cms_user -u corley -p admin1 -e cl@126.com
添加之后,运行主程序,显示:
显然,在登录之后,跳转到后台首页。
但是现在并未对邮箱和密码进行验证,需要做进一步的数据库验证:
config.py如下:
import os
# 数据库连接配置
HOSTNAME = '127.0.0.1'
PORT = 3306
USERNAME = 'root'
PASSWORD = 'root'
DATABASE = 'flask_bbs'
DB_URL = 'mysql+mysqlconnector://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)
SQLALCHEMY_DATABASE_URI = DB_URL
SQLALCHEMY_TRACK_MODIFICATIONS = False
# 设置密钥
SECRET_KEY = os.urandom(15)
模型修改如下:
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
from exts import db
class CMSUser(db.Model):
'''后台管理员用户类'''
__tablename__ = 'cms_user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(30), nullable=False)
_password = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(50), nullable=False, unique=True)
join_time = db.Column(db.DateTime, default=datetime.now())
def __init__(self, username, password, email):
self.username = username
self.password = password
self.email = email
@property
def password(self):
'''获取密码'''
return self._password
@password.setter
def password(self, raw_password):
'''设置密码'''
self._password = generate_password_hash(raw_password)
def check_password(self, raw_password):
'''验证密码是否正确'''
result = check_password_hash(self.password, raw_password)
return result
视图函数views.py如下:
from flask import Blueprint, render_template, views, request, redirect, url_for, session
from apps.cms.forms import LoginForm
from apps.cms.models import CMSUser
cms_bp = Blueprint('cms', __name__, url_prefix='/cms')
@cms_bp.route('/')
def index():
return '后台管理首页'
class LoginView(views.MethodView):
def get(self):
return render_template('cms/cms_login.html')
def post(self):
login_form = LoginForm(request.form)
# 判断表单是否验证
if login_form.validate():
# 获取数据
email = login_form.email.data
password = login_form.password.data
remember = login_form.remember.data
user = CMSUser.query.filter_by(email=email).first()
# 根据用户验证密码是否正确
if user and user.check_password(password):
# 将用户id记录入session
session['user_id'] = user.id
# 如果记住密码,需要持久化
if remember:
session.permanent = True
# 登录成功,则跳转到后台首页
return redirect(url_for('cms.index'))
else:
return '邮箱或密码有误'
else:
print(login_form.errors)
return '登录验证错误'
cms_bp.add_url_rule('/login/', view_func=LoginView.as_view('login'))
显示:
此时实现了登录的验证。
二、CMS用户错误信息返回
当然,在用户名或密码验证失败时,不应该简单地返回一个字符串,而是应该渲染一个模板,并且传入登录信息作为参数。
views.py修改如下:
from flask import Blueprint, render_template, views, request, redirect, url_for, session
from apps.cms.forms import LoginForm
from apps.cms.models import CMSUser
cms_bp = Blueprint('cms', __name__, url_prefix='/cms')
@cms_bp.route('/')
def index():
return '后台管理首页'
class LoginView(views.MethodView):
def get(self, message=None):
return render_template('cms/cms_login.html', message=message)
def post(self):
login_form = LoginForm(request.form)
# 判断表单是否验证
if login_form.validate():
# 获取数据
email = login_form.email.data
password = login_form.password.data
remember = login_form.remember.data
user = CMSUser.query.filter_by(email=email).first()
# 根据用户验证密码是否正确
if user and user.check_password(password):
# 将用户id记录入session
session['user_id'] = user.id
# 如果记住密码,需要持久化
if remember:
session.permanent = True
# 登录成功,则跳转到后台首页
return redirect(url_for('cms.index'))
else:
return self.get(message='邮箱或密码有误')
else:
print(login_form.errors)
return '登录验证错误'
cms_bp.add_url_rule('/login/', view_func=LoginView.as_view('login'))
cms_login.html修改如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="{{ url_for('static', filename='cms/images/bbs-favicon.ico') }}">
<title>CMS用户登录</title>
<!-- Bootstrap core CSS -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="{{ url_for('static', filename='cms/css/signin.css') }}" rel="stylesheet">
<![endif]-->
</head>
<body>
<div class="container">
<form class="form-signin" method="post">
<h2 class="form-signin-heading">请登录</h2>
<label for="inputEmail" class="sr-only">邮箱地址</label>
<input type="email" id="inputEmail" class="form-control" name="email" placeholder="邮箱地址" required autofocus>
<label for="inputPassword" class="sr-only">密码</label>
<input type="password" id="inputPassword" class="form-control" name="password" placeholder="密码" required>
<div class="checkbox">
<label>
<input type="checkbox" value="1" name="remember"> 记住我
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">立即登录</button>
</form>
{% if message %}
<p style="text-align: center" class="text-danger">{{ message }}</p>
{% endif %}
</div> <!-- /container -->
</body>
</html>
显示:
除此之外,在验证失败时,也应该渲染一个模板。
views.py如下:
from flask import Blueprint, render_template, views, request, redirect, url_for, session
from apps.cms.forms import LoginForm
from apps.cms.models import CMSUser
cms_bp = Blueprint('cms', __name__, url_prefix='/cms')
@cms_bp.route('/')
def index():
return '后台管理首页'
class LoginView(views.MethodView):
def get(self, message=None):
return render_template('cms/cms_login.html', message=message)
def post(self):
login_form = LoginForm(request.form)
# 判断表单是否验证
if login_form.validate():
# 获取数据
email = login_form.email.data
password = login_form.password.data
remember = login_form.remember.data
user = CMSUser.query.filter_by(email=email).first()
# 根据用户验证密码是否正确
if user and user.check_password(password):
# 将用户id记录入session
session['user_id'] = user.id
# 如果记住密码,需要持久化
if remember:
session.permanent = True
# 登录成功,则跳转到后台首页
return redirect(url_for('cms.index'))
else:
return self.get(message='邮箱或密码有误')
else:
print(login_form.errors)
return self.get(message=login_form.errors.popitem()[1][0])
cms_bp.add_url_rule('/login/', view_func=LoginView.as_view('login'))
显示:
三、CMS用户登录限制和CSRF保护
1.登录验证
通过钩子函数实现访问后台首页前验证是否已经登录:
from flask import Blueprint, render_template, views, request, redirect, url_for, session
from apps.cms.forms import LoginForm
from apps.cms.models import CMSUser
cms_bp = Blueprint('cms', __name__, url_prefix='/cms')
@cms_bp.before_request
def before_request():
# 判断当前是否是登录页面
if not request.path.endswith(url_for('cms.login')):
user_id = session.get('user_id')
# 判断是否保存到session
if not user_id:
return redirect(url_for('cms.login'))
@cms_bp.route('/')
def index():
return '后台管理首页'
class LoginView(views.MethodView):
def get(self, message=None):
return render_template('cms/cms_login.html', message=message)
def post(self):
login_form = LoginForm(request.form)
# 判断表单是否验证
if login_form.validate():
# 获取数据
email = login_form.email.data
password = login_form.password.data
remember = login_form.remember.data
user = CMSUser.query.filter_by(email=email).first()
# 根据用户验证密码是否正确
if user and user.check_password(password):
# 将用户id记录入session
session['user_id'] = user.id
# 如果记住密码,需要持久化
if remember:
session.permanent = True
# 登录成功,则跳转到后台首页
return redirect(url_for('cms.index'))
else:
return self.get(message='邮箱或密码有误')
else:
print(login_form.errors)
return self.get(message=login_form.errors.popitem()[1][0])
cms_bp.add_url_rule('/login/', view_func=LoginView.as_view('login'))
显示:
显然,未登录时,直接跳转到登录页面。
还可以使用装饰器实现,新建decorators.py如下:
from flask import session, redirect, url_for
def login_required(func):
def index(*args, **kwargs):
if 'user_id' in session:
return func(*args, **kwargs)
else:
return redirect(url_for('cms.login'))
return index
views.py如下:
from flask import Blueprint, render_template, views, request, redirect, url_for, session
from apps.cms.forms import LoginForm
from apps.cms.models import CMSUser
from .decorators import login_required # .代表当前路径
cms_bp = Blueprint('cms', __name__, url_prefix='/cms')
# @cms_bp.before_request
# def before_request():
# # 判断当前是否是登录页面
# if not request.path.endswith(url_for('cms.login')):
# user_id = session.get('user_id')
# # 判断是否保存到session
# if not user_id:
# return redirect(url_for('cms.login'))
@cms_bp.route('/')
@login_required
def index():
return '后台管理首页'
class LoginView(views.MethodView):
def get(self, message=None):
return render_template('cms/cms_login.html', message=message)
def post(self):
login_form = LoginForm(request.form)
# 判断表单是否验证
if login_form.validate():
# 获取数据
email = login_form.email.data
password = login_form.password.data
remember = login_form.remember.data
user = CMSUser.query.filter_by(email=email).first()
# 根据用户验证密码是否正确
if user and user.check_password(password):
# 将用户id记录入session
session['user_id'] = user.id
# 如果记住密码,需要持久化
if remember:
session.permanent = True
# 登录成功,则跳转到后台首页
return redirect(url_for('cms.index'))
else:
return self.get(message='邮箱或密码有误')
else:
print(login_form.errors)
return self.get(message=login_form.errors.popitem()[1][0])
cms_bp.add_url_rule('/login/', view_func=LoginView.as_view('login'))
可以达到同样的效果。
注意:
装饰器函数中的子函数名必须是index,否则可能会报错:
Traceback (most recent call last):
File "D:\.virtualenvs\Test-gftU5mTd\lib\site-packages\flask\app.py", line 2464, in __call__
return self.wsgi_app(environ, start_response)
File "D:\.virtualenvs\Test-gftU5mTd\lib\site-packages\flask\app.py", line 2450, in wsgi_app
response = self.handle_exception(e)
File "D:\.virtualenvs\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1867, in handle_exception
reraise(exc_type, exc_value, tb)
File "D:\.virtualenvs\Test-gftU5mTd\lib\site-packages\flask\_compat.py", line 39, in reraise
raise value
File "D:\.virtualenvs\Test-gftU5mTd\lib\site-packages\flask\app.py", line 2447, in wsgi_app
response = self.full_dispatch_request()
File "D:\.virtualenvs\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1952, in full_dispatch_request
rv = self.handle_user_exception(e)
File "D:\.virtualenvs\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1821, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "D:\.virtualenvs\Test-gftU5mTd\lib\site-packages\flask\_compat.py", line 39, in reraise
raise value
File "D:\.virtualenvs\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1950, in full_dispatch_request
rv = self.dispatch_request()
File "D:\.virtualenvs\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1936, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "D:\.virtualenvs\Test-gftU5mTd\lib\site-packages\flask\views.py", line 89, in view
return self.dispatch_request(*args, **kwargs)
File "D:\.virtualenvs\Test-gftU5mTd\lib\site-packages\flask\views.py", line 163, in dispatch_request
return meth(*args, **kwargs)
File "XXX\Flask_BBS\apps\cms\views.py", line 44, in post
return redirect(url_for('cms.index'))
File "D:\.virtualenvs\Test-gftU5mTd\lib\site-packages\flask\helpers.py", line 370, in url_for
return appctx.app.handle_url_build_error(error, endpoint, values)
File "D:\.virtualenvs\Test-gftU5mTd\lib\site-packages\flask\app.py", line 2216, in handle_url_build_error
reraise(exc_type, exc_value, tb)
File "D:\.virtualenvs\Test-gftU5mTd\lib\site-packages\flask\_compat.py", line 39, in reraise
raise value
File "D:\.virtualenvs\Test-gftU5mTd\lib\site-packages\flask\helpers.py", line 358, in url_for
endpoint, values, method=method, force_external=external
File "D:\.virtualenvs\Test-gftU5mTd\lib\site-packages\werkzeug\routing.py", line 2179, in build
raise BuildError(endpoint, values, method, self)
werkzeug.routing.BuildError: Could not build url for endpoint 'cms.index'. Did you mean 'cms.inner' instead?
如果遇到类似的报错,请保持装饰器中的子函数名与所要装饰的视图函数名保持一致,即可解决。
显然,钩子函数更易于定义和使用,因此建议使用钩子函数,并且钩子函数也可以定义在专门的文件中,新定义hooks.py如下:
from flask import request, session, url_for, redirect
from .views import cms_bp
@cms_bp.before_request
def before_request():
# 判断当前是否是登录页面
if not request.path.endswith(url_for('cms.login')):
user_id = session.get('user_id')
# 判断是否保存到session
if not user_id:
return redirect(url_for('cms.login'))
views.py如下:
from flask import Blueprint, render_template, views, request, redirect, url_for, session
from apps.cms.forms import LoginForm
from apps.cms.models import CMSUser
# from .decorators import login_required # .代表当前路径
cms_bp = Blueprint('cms', __name__, url_prefix='/cms')
from .hooks import before_request
@cms_bp.route('/')
# @login_required
def index():
return '后台管理首页'
class LoginView(views.MethodView):
def get(self, message=None):
return render_template('cms/cms_login.html', message=message)
def post(self):
login_form = LoginForm(request.form)
# 判断表单是否验证
if login_form.validate():
# 获取数据
email = login_form.email.data
password = login_form.password.data
remember = login_form.remember.data
user = CMSUser.query.filter_by(email=email).first()
# 根据用户验证密码是否正确
if user and user.check_password(password):
# 将用户id记录入session
session['user_id'] = user.id
# 如果记住密码,需要持久化
if remember:
session.permanent = True
# 登录成功,则跳转到后台首页
return redirect(url_for('cms.index'))
else:
return self.get(message='邮箱或密码有误')
else:
print(login_form.errors)
return self.get(message=login_form.errors.popitem()[1][0])
cms_bp.add_url_rule('/login/', view_func=LoginView.as_view('login'))
2.CSRF保护
CSRF即跨站请求伪造,是源于WEB的隐式身份验证机制,
一般的WEB身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的。
例如,用户登录受信任的网址A,在本地生成了Cookie,在Cookie没有失效的情况下去访问了危险网站B,B可能会盗用你的身份,以你的名义发送恶意请求、盗取账号、设置购买商品等,造成个人隐私泄露和财产安全。
Flask中Flask-WTF表单保护用户免受CSRF威胁,可以保证表单请求的真实可靠,达到保护网站的作用。
在有不包含表单的视图时,页面请求仍需要保护。
使用如下:
主程序bbs.py中设置全局CSRF保护如下:
''' 前台 front 后台 cms 公有 common '''
from flask import Flask
from flask_wtf import CSRFProtect
from exts import db
from apps.cms.views import cms_bp
from apps.front.views import front_bp
import config
app = Flask(__name__)
CSRFProtect(app)
app.config.from_object(config)
app.config['TEMPLATE_AUTO_RELOAD'] = True
db.init_app(app)
app.register_blueprint(cms_bp)
app.register_blueprint(front_bp)
if __name__ == '__main__':
app.run(debug=True)
cms_login.html表单增加CSRF验证如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="{{ url_for('static', filename='cms/images/bbs-favicon.ico') }}">
<title>CMS用户登录</title>
<!-- Bootstrap core CSS -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="{{ url_for('static', filename='cms/css/signin.css') }}" rel="stylesheet">
<![endif]-->
</head>
<body>
<div class="container">
<form class="form-signin" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<h2 class="form-signin-heading">请登录</h2>
<label for="inputEmail" class="sr-only">邮箱地址</label>
<input type="email" id="inputEmail" class="form-control" name="email" placeholder="邮箱地址" required autofocus>
<label for="inputPassword" class="sr-only">密码</label>
<input type="password" id="inputPassword" class="form-control" name="password" placeholder="密码" required>
<div class="checkbox">
<label>
<input type="checkbox" value="1" name="remember"> 记住我
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">立即登录</button>
</form>
{% if message %}
<p style="text-align: center" class="text-danger">{{ message }}</p>
{% endif %}
</div> <!-- /container -->
</body>
</html>
显示:
显然,此时页面中多了CSRF验证的随机字符串(虽然是被隐藏的)。
四、CMS用户名渲染和注销功能
1.后台页面基本实现
在templates/cms下创建cms_index.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
<script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
<link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='cms/css/cms_base.css') }}">
<script src="{{ url_for('static', filename='cms/js/cms_base.js') }}"></script>
{% block head %}
{% endblock %}
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">熊熊乐园论坛CMS管理系统</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">小柯<span>[超级管理员]</span></a></li>
<li><a href="#">注销</a></li>
</ul>
<form class="navbar-form navbar-right">
<input type="text" class="form-control" placeholder="查找...">
</form>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav-sidebar">
<li class="unfold"><a href="#">首页</a></li>
<li class="profile-li">
<a href="#">个人中心<span></span></a>
<ul class="subnav">
<li><a href="#">个人信息</a></li>
<li><a href="#">修改密码</a></li>
<li><a href="#">修改邮箱</a></li>
</ul>
</li>
<li class="nav-group post-manage"><a href="#">帖子管理</a></li>
<li class="comments-manage"><a href="#">评论管理</a></li>
<li class="board-manage"><a href="#">板块管理</a></li>
<li class="nav-group user-manage"><a href="#">用户管理</a></li>
<li class="role-manage"><a href="#">组管理</a></li>
<li class="nav-group cmsuser-manage"><a href="#">CMS用户管理</a></li>
<li class="cmsrole-manage"><a href="#">CMS组管理</a></li>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1>{% block page_title %} {% endblock %}</h1>
<div class="main_content">
{% block content %} {% endblock %}
</div>
</div>
</div>
</div>
</body>
</html>
在static/cms/css下创建cms_base.css如下:
body {
padding-top: 50px;
overflow: hidden;
}
.sub-header {
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.navbar-fixed-top {
border: 0;
}
.sidebar {
display: none;
}
@media (min-width: 768px) {
.sidebar {
position: fixed;
top: 51px;
bottom: 0;
left: 0;
z-index: 1000;
display: block;
padding: 20px;
overflow-x: hidden;
overflow-y: auto;
background-color: #363a47;
border-right: 1px solid #eee;
margin-top: -1px;
}
}
.nav-sidebar {
padding: 5px 0;
margin-left: -20px;
margin-right: -20px;
}
.nav-sidebar > li {
background: #494f60;
border-bottom: 1px solid #363a47;
border-top: 1px solid #666;
line-height: 35px;
}
.nav-sidebar > li > a {
background: #494f60;
color: #9b9fb1;
margin-left: 25px;
display: block;
}
.nav-sidebar > li a span {
float: right;
width: 10px;
height: 10px;
border-style: solid;
border-color: #9b9fb1 #9b9fb1 transparent transparent;
border-width: 1px;
transform: rotate(45deg);
position: relative;
top: 10px;
margin-right: 10px;
}
.nav-sidebar > li > a:hover {
color: #fff;
background: #494f60;
text-decoration: none;
}
.nav-sidebar > li > .subnav {
display: none;
}
.nav-sidebar > li.unfold {
background: #494f60;
}
.nav-sidebar > li.unfold > .subnav {
display: block;
}
.nav-sidebar > li.unfold > a {
color: #db4055;
}
.nav-sidebar > li.unfold > a span {
transform: rotate(135deg);
top: 5px;
border-color: #db4055 #db4055 transparent transparent;
}
.subnav {
padding-left: 10px;
padding-right: 10px;
background: #363a47;
overflow: hidden;
}
.subnav li {
overflow: hidden;
margin-top: 10px;
line-height: 25px;
height: 25px;
}
.subnav li.active {
background: #db4055;
}
.subnav li a {
color: #9b9fb1;
padding-left: 30px;
height: 25px;
line-height: 25px;
}
.subnav li a:hover {
color: #fff;
}
.nav-group {
margin-top: 10px;
}
.main {
padding: 20px;
}
@media (min-width: 768px) {
.main {
padding-right: 40px;
padding-left: 40px;
}
}
.main .page-header {
margin-top: 0;
}
.placeholders {
margin-bottom: 30px;
text-align: center;
}
.placeholders h4 {
margin-bottom: 0;
}
.placeholder {
margin-bottom: 20px;
}
.placeholder img {
display: inline-block;
border-radius: 50%;
}
.main_content {
margin-top: 20px;
}
.top-group {
padding: 5px 10px;
border-radius: 2px;
background: #ecedf0;
overflow: hidden;
}
在static/cms下创建js目录,下面创建cms_base.js和resetpwd.js,cms_base.js如下:
$(function () {
$('.nav-sidebar>li>a').click(function (event) {
var that = $(this);
if (that.children('a').attr('href') == '#') {
event.preventDefault();
}
if (that.parent().hasClass('unfold')) {
that.parent().removeClass('unfold');
} else {
that.parent().addClass('unfold').siblings().removeClass('unfold');
}
console.log('coming....');
});
$('.nav-sidebar a').mouseleave(function () {
$(this).css('text-decoration', 'none');
});
});
$(function () {
var url = window.location.href;
if (url.indexOf('profile') >= 0) {
var profileLi = $('.profile-li');
profileLi.addClass('unfold').siblings().removeClass('unfold');
profileLi.children('.subnav').children().eq(0).addClass('active').siblings().removeClass('active');
} else if (url.indexOf('resetpwd') >= 0) {
var profileLi = $('.profile-li');
profileLi.addClass('unfold').siblings().removeClass('unfold');
profileLi.children('.subnav').children().eq(1).addClass('active').siblings().removeClass('active');
} else if (url.indexOf('resetemail') >= 0) {
var profileLi = $('.profile-li');
profileLi.addClass('unfold').siblings().removeClass('unfold');
profileLi.children('.subnav').children().eq(2).addClass('active').siblings().removeClass('active');
} else if (url.indexOf('posts') >= 0) {
var postManageLi = $('.post-manage');
postManageLi.addClass('unfold').siblings().removeClass('unfold');
} else if (url.indexOf('boards') >= 0) {
var boardManageLi = $('.board-manage');
boardManageLi.addClass('unfold').siblings().removeClass('unfold');
} else if (url.indexOf('permissions') >= 0) {
var permissionManageLi = $('.permission-manage');
permissionManageLi.addClass('unfold').siblings().removeClass('unfold');
} else if (url.indexOf('roles') >= 0) {
var roleManageLi = $('.role-manage');
roleManageLi.addClass('unfold').siblings().removeClass('unfold');
} else if (url.indexOf('users') >= 0) {
var userManageLi = $('.user-manage');
userManageLi.addClass('unfold').siblings().removeClass('unfold');
} else if (url.indexOf('cmsuser_manage') >= 0) {
var cmsuserManageLi = $('.cmsuser-manage');
cmsuserManageLi.addClass('unfold').siblings().removeClass('unfold');
} else if (url.indexOf('cmsrole_manage') >= 0) {
var cmsroleManageLi = $('.cmsrole-manage');
cmsroleManageLi.addClass('unfold').siblings().removeClass('unfold');
} else if (url.indexOf('comments') >= 0) {
var commentsManageLi = $('.comments-manage');
commentsManageLi.addClass('unfold').siblings().removeClass('unfold');
}
});
resetpwd.js如下:
$(function () {
$("#submit").click(function (event) {
// event.preventDefault
// 是阻止按钮默认的提交表单的事件
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();
// 1. 要在模版的meta标签中渲染一个csrf-token
// 2. 在ajax请求的头部中设置X-CSRFtoken
var lgajax = {
'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 csrftoken = $('meta[name=csrf-token]').attr('content');
var csrftoken = $('input[name=csrf-token]').attr('value');
xhr.setRequestHeader("X-CSRFToken", csrftoken)
}
}
});
}
};
lgajax.post({
'url': '/cms/resetpwd/',
'data': {
'oldpwd': oldpwd,
'newpwd': newpwd,
'newpwd2': newpwd2
},
'success': function (data) {
console.log(data);
},
'fail': function (error) {
console.log(error);
}
});
});
});
views.py修改如下:
from flask import Blueprint, render_template, views, request, redirect, url_for, session
from apps.cms.forms import LoginForm
from apps.cms.models import CMSUser
# from .decorators import login_required # .代表当前路径
cms_bp = Blueprint('cms', __name__, url_prefix='/cms')
from .hooks import before_request
@cms_bp.route('/')
# @login_required
def index():
return render_template('cms/cms_index.html')
class LoginView(views.MethodView):
def get(self, message=None):
return render_template('cms/cms_login.html', message=message)
def post(self):
login_form = LoginForm(request.form)
# 判断表单是否验证
if login_form.validate():
# 获取数据
email = login_form.email.data
password = login_form.password.data
remember = login_form.remember.data
user = CMSUser.query.filter_by(email=email).first()
# 根据用户验证密码是否正确
if user and user.check_password(password):
# 将用户id记录入session
session['user_id'] = user.id
# 如果记住密码,需要持久化
if remember:
session.permanent = True
# 登录成功,则跳转到后台首页
return redirect(url_for('cms.index'))
else:
return self.get(message='邮箱或密码有误')
else:
print(login_form.errors)
return self.get(message=login_form.errors.popitem()[1][0])
cms_bp.add_url_rule('/login/', view_func=LoginView.as_view('login'))
显示:
显然,已经实现后台管理的基本效果。
2.用户名渲染
现进行用户名渲染,hooks.py如下:
from flask import request, session, url_for, redirect, g
from .models import CMSUser
from .views import cms_bp
@cms_bp.before_request
def before_request():
# 判断当前是否是登录页面
if not request.path.endswith(url_for('cms.login')):
user_id = session.get('user_id')
# 判断是否保存到session
if not user_id:
return redirect(url_for('cms.login'))
if 'user_id' in session:
user_id = session.get('user_id')
user = CMSUser.query.get(user_id)
# 使用g对象保存用户
if user:
g.cms_user = user
cms_index.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
<script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
<link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='cms/css/cms_base.css') }}">
<script src="{{ url_for('static', filename='cms/js/cms_base.js') }}"></script>
{% block head %}
{% endblock %}
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">熊熊乐园论坛CMS管理系统</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">{{ g.cms_user.username }}<span>[超级管理员]</span></a></li>
<li><a href="#">注销</a></li>
</ul>
<form class="navbar-form navbar-right">
<input type="text" class="form-control" placeholder="查找...">
</form>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav-sidebar">
<li class="unfold"><a href="#">首页</a></li>
<li class="profile-li">
<a href="#">个人中心<span></span></a>
<ul class="subnav">
<li><a href="#">个人信息</a></li>
<li><a href="#">修改密码</a></li>
<li><a href="#">修改邮箱</a></li>
</ul>
</li>
<li class="nav-group post-manage"><a href="#">帖子管理</a></li>
<li class="comments-manage"><a href="#">评论管理</a></li>
<li class="board-manage"><a href="#">板块管理</a></li>
<li class="nav-group user-manage"><a href="#">用户管理</a></li>
<li class="role-manage"><a href="#">组管理</a></li>
<li class="nav-group cmsuser-manage"><a href="#">CMS用户管理</a></li>
<li class="cmsrole-manage"><a href="#">CMS组管理</a></li>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1>{% block page_title %} {% endblock %}</h1>
<div class="main_content">
{% block content %} {% endblock %}
</div>
</div>
</div>
</div>
</body>
</html>
显示:
显然,已经根据登录信息渲染出用户名。
3.注销功能实现
注销主要包括两方面:
- 清空或删除session
- 重定向到登录页面
现实现注销功能,views.py如下:
from flask import Blueprint, render_template, views, request, redirect, url_for, session
from apps.cms.forms import LoginForm
from apps.cms.models import CMSUser
# from .decorators import login_required # .代表当前路径
cms_bp = Blueprint('cms', __name__, url_prefix='/cms')
from .hooks import before_request
@cms_bp.route('/')
# @login_required
def index():
return render_template('cms/cms_index.html')
@cms_bp.route('/logout/')
def logout():
# 清空session
del session['user_id']
# 重定向到登录页面
return redirect(url_for('cms.login'))
class LoginView(views.MethodView):
def get(self, message=None):
return render_template('cms/cms_login.html', message=message)
def post(self):
login_form = LoginForm(request.form)
# 判断表单是否验证
if login_form.validate():
# 获取数据
email = login_form.email.data
password = login_form.password.data
remember = login_form.remember.data
user = CMSUser.query.filter_by(email=email).first()
# 根据用户验证密码是否正确
if user and user.check_password(password):
# 将用户id记录入session
session['user_id'] = user.id
# 如果记住密码,需要持久化
if remember:
session.permanent = True
# 登录成功,则跳转到后台首页
return redirect(url_for('cms.index'))
else:
return self.get(message='邮箱或密码有误')
else:
print(login_form.errors)
return self.get(message=login_form.errors.popitem()[1][0])
cms_bp.add_url_rule('/login/', view_func=LoginView.as_view('login'))
cms_index.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
<script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
<link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='cms/css/cms_base.css') }}">
<script src="{{ url_for('static', filename='cms/js/cms_base.js') }}"></script>
{% block head %}
{% endblock %}
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">熊熊乐园论坛CMS管理系统</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">{{ g.cms_user.username }}<span>[超级管理员]</span></a></li>
<li><a href="{{ url_for('cms.logout') }}">注销</a></li>
</ul>
<form class="navbar-form navbar-right">
<input type="text" class="form-control" placeholder="查找...">
</form>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav-sidebar">
<li class="unfold"><a href="#">首页</a></li>
<li class="profile-li">
<a href="#">个人中心<span></span></a>
<ul class="subnav">
<li><a href="#">个人信息</a></li>
<li><a href="#">修改密码</a></li>
<li><a href="#">修改邮箱</a></li>
</ul>
</li>
<li class="nav-group post-manage"><a href="#">帖子管理</a></li>
<li class="comments-manage"><a href="#">评论管理</a></li>
<li class="board-manage"><a href="#">板块管理</a></li>
<li class="nav-group user-manage"><a href="#">用户管理</a></li>
<li class="role-manage"><a href="#">组管理</a></li>
<li class="nav-group cmsuser-manage"><a href="#">CMS用户管理</a></li>
<li class="cmsrole-manage"><a href="#">CMS组管理</a></li>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1>{% block page_title %} {% endblock %}</h1>
<div class="main_content">
{% block content %} {% endblock %}
</div>
</div>
</div>
</div>
</body>
</html>
显示:
显然,实现了注销功能,注销后直接重定向到登录页面,再访问后台管理首页也会重定向到登录页。
五、CMS个人页面和模板抽离
1.个人信息页面实现
实现后台个人信息页面,views.py如下:
from flask import Blueprint, render_template, views, request, redirect, url_for, session
from apps.cms.forms import LoginForm
from apps.cms.models import CMSUser
# from .decorators import login_required # .代表当前路径
cms_bp = Blueprint('cms', __name__, url_prefix='/cms')
from .hooks import before_request
@cms_bp.route('/')
# @login_required
def index():
return render_template('cms/cms_index.html')
@cms_bp.route('/logout/')
def logout():
# 清空session
del session['user_id']
# 重定向到登录页面
return redirect(url_for('cms.login'))
@cms_bp.route("/profile/")
def profile():
return render_template("cms/cms_profile.html")
class LoginView(views.MethodView):
def get(self, message=None):
return render_template('cms/cms_login.html', message=message)
def post(self):
login_form = LoginForm(request.form)
# 判断表单是否验证
if login_form.validate():
# 获取数据
email = login_form.email.data
password = login_form.password.data
remember = login_form.remember.data
user = CMSUser.query.filter_by(email=email).first()
# 根据用户验证密码是否正确
if user and user.check_password(password):
# 将用户id记录入session
session['user_id'] = user.id
# 如果记住密码,需要持久化
if remember:
session.permanent = True
# 登录成功,则跳转到后台首页
return redirect(url_for('cms.index'))
else:
return self.get(message='邮箱或密码有误')
else:
print(login_form.errors)
return self.get(message=login_form.errors.popitem()[1][0])
cms_bp.add_url_rule('/login/', view_func=LoginView.as_view('login'))
cms_index.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
<script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
<link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='cms/css/cms_base.css') }}">
<script src="{{ url_for('static', filename='cms/js/cms_base.js') }}"></script>
{% block head %}
{% endblock %}
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">熊熊乐园论坛CMS管理系统</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">{{ g.cms_user.username }}<span>[超级管理员]</span></a></li>
<li><a href="{{ url_for('cms.logout') }}">注销</a></li>
</ul>
<form class="navbar-form navbar-right">
<input type="text" class="form-control" placeholder="查找...">
</form>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav-sidebar">
<li class="unfold"><a href="#">首页</a></li>
<li class="profile-li">
<a href="#">个人中心<span></span></a>
<ul class="subnav">
<li><a href="{{ url_for('cms.profile') }}">个人信息</a></li>
<li><a href="#">修改密码</a></li>
<li><a href="#">修改邮箱</a></li>
</ul>
</li>
<li class="nav-group post-manage"><a href="#">帖子管理</a></li>
<li class="comments-manage"><a href="#">评论管理</a></li>
<li class="board-manage"><a href="#">板块管理</a></li>
<li class="nav-group user-manage"><a href="#">用户管理</a></li>
<li class="role-manage"><a href="#">组管理</a></li>
<li class="nav-group cmsuser-manage"><a href="#">CMS用户管理</a></li>
<li class="cmsrole-manage"><a href="#">CMS组管理</a></li>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1>熊熊论坛</h1>
<div class="main_content">
<h3>欢迎来到熊熊论坛</h3>
</div>
</div>
</div>
</div>
</body>
</html>
新建cms_profile.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>cms-个人中心</title>
<script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
<link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='cms/css/cms_base.css') }}">
<script src="{{ url_for('static', filename='cms/js/cms_base.js') }}"></script>
{% block head %}
{% endblock %}
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">熊熊乐园论坛CMS管理系统</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">{{ g.cms_user.username }}<span>[超级管理员]</span></a></li>
<li><a href="{{ url_for('cms.logout') }}">注销</a></li>
</ul>
<form class="navbar-form navbar-right">
<input type="text" class="form-control" placeholder="查找...">
</form>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav-sidebar">
<li class="unfold"><a href="#">首页</a></li>
<li class="profile-li">
<a href="#">个人中心<span></span></a>
<ul class="subnav">
<li><a href="{{ url_for('cms.profile') }}">个人信息</a></li>
<li><a href="#">修改密码</a></li>
<li><a href="#">修改邮箱</a></li>
</ul>
</li>
<li class="nav-group post-manage"><a href="#">帖子管理</a></li>
<li class="comments-manage"><a href="#">评论管理</a></li>
<li class="board-manage"><a href="#">板块管理</a></li>
<li class="nav-group user-manage"><a href="#">用户管理</a></li>
<li class="role-manage"><a href="#">组管理</a></li>
<li class="nav-group cmsuser-manage"><a href="#">CMS用户管理</a></li>
<li class="cmsrole-manage"><a href="#">CMS组管理</a></li>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1>个人中心</h1>
<div class="main_content">
<table class="table table-bordered">
<tr>
<td>用户名:</td>
<td>Corley</td>
</tr>
<tr>
<td>邮箱:</td>
<td>cl@126.com</td>
</tr>
<tr>
<td>角色:</td>
<td>暂无</td>
</tr>
<tr>
<td>权限:</td>
<td>暂无</td>
</tr>
<tr>
<td>加入时间:</td>
<td>2020-05-21</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</body>
</html>
显示:
显然,可以显示管理员用户的基本信息。
2.模板抽离
很明显,cms_index.html和cms_profile.html有大段重复的代码,因此可以使用模板继承简化代码。
新建cms_base.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>
{% block title %}
{% endblock %}
</title>
<script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
<link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='cms/css/cms_base.css') }}">
<script src="{{ url_for('static', filename='cms/js/cms_base.js') }}"></script>
{% block head %}
{% endblock %}
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">熊熊乐园论坛CMS管理系统</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">{{ g.cms_user.username }}<span>[超级管理员]</span></a></li>
<li><a href="{{ url_for('cms.logout') }}">注销</a></li>
</ul>
<form class="navbar-form navbar-right">
<input type="text" class="form-control" placeholder="查找...">
</form>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav-sidebar">
<li class="unfold"><a href="#">首页</a></li>
<li class="profile-li">
<a href="#">个人中心<span></span></a>
<ul class="subnav">
<li><a href="{{ url_for('cms.profile') }}">个人信息</a></li>
<li><a href="#">修改密码</a></li>
<li><a href="#">修改邮箱</a></li>
</ul>
</li>
<li class="nav-group post-manage"><a href="#">帖子管理</a></li>
<li class="comments-manage"><a href="#">评论管理</a></li>
<li class="board-manage"><a href="#">板块管理</a></li>
<li class="nav-group user-manage"><a href="#">用户管理</a></li>
<li class="role-manage"><a href="#">组管理</a></li>
<li class="nav-group cmsuser-manage"><a href="#">CMS用户管理</a></li>
<li class="cmsrole-manage"><a href="#">CMS组管理</a></li>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1>{% block page_title %} {% endblock %}</h1>
<div class="main_content">
{% block content %} {% endblock %}
</div>
</div>
</div>
</div>
</body>
</html>
cms_index.html修改如下:
{% extends 'cms/cms_base.html' %}
{% block title %}
熊熊论坛后台
{% endblock %}
{% block page_title %}
熊熊论坛
{% endblock %}
{% block content %}
欢迎来到{{ self.page_title() }}......
{% endblock %}
cms_profile.html修改如下:
{% extends 'cms/cms_base.html' %}
{% block title %}
熊熊论坛后台个人信息
{% endblock %}
{% block page_title %}
熊熊个人中心
{% endblock %}
{% block content %}
{% set user = g.cms_user %}
<table class="table table-bordered">
<tr>
<td>用户名:</td>
<td>{{ user.username }}</td>
</tr>
<tr>
<td>邮箱:</td>
<td>{{ user.email }}</td>
</tr>
<tr>
<td>角色:</td>
<td>暂无</td>
</tr>
<tr>
<td>权限:</td>
<td>暂无</td>
</tr>
<tr>
<td>加入时间:</td>
<td>{{ user.join_time }}</td>
</tr>
</table>
{% endblock %}
显示:
显然,模板抽离之后,也实现了基本功能,并且代码更简洁。