Python全栈(八)Flask项目实战之2.CMS后台功能开发

   日期:2020-05-25     浏览:93    评论:0    
核心提示:用户登录验证需要实现表单验证,视图类中使用post函数实现表单提交,还需要进行邮箱和密码的验证。在用户名或密码验证失败或表单验证失败时应返回模板,get和post方法可以进行统一。登录验证有两种实现方式,使用钩子函数和使用装饰器,都可以单独建立文件,建议使用钩子函数;CSRF保护可以保证表单请求的真实可靠,模板表单中也要增加验证。后台页面基本实现包括模板和静态文件;用户名渲染在钩子函数中定义,并绑定到g对象;注销通过清空session和重定向实现。使用模板实现后台个人信息页面;通过模板继承来简化代码。py

文章目录

  • 一、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 %}

显示:

显然,模板抽离之后,也实现了基本功能,并且代码更简洁。

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
更多>相关资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服