2.1.cms后台登录界面完成
(1)templates/cms/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=""> <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"> </head> <body> <div class="container"> <form class="form-signin"> <h2 class="form-signin-heading">请登录</h2> <label for="inputEmail" class="sr-only">邮箱:</label> <input type="email" id="inputEmail" class="form-control" placeholder="邮箱" required autofocus> <label for="inputPassword" class="sr-only">密吗</label> <input type="password" id="inputPassword" class="form-control" placeholder="密码" required> <div class="checkbox"> <label> <input type="checkbox" value="remember-me"> 记住我 </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit">立即登录</button> </form> </div> <!-- /container --> </body> </html>
(2)static/cms/css/signin.css
body { padding-top: 40px; padding-bottom: 40px; background-color: #eee; } .form-signin { max-width: 330px; padding: 15px; margin: 0 auto; } .form-signin .form-signin-heading, .form-signin .checkbox { margin-bottom: 10px; } .form-signin .checkbox { font-weight: normal; } .form-signin .form-control { position: relative; height: auto; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding: 10px; font-size: 16px; } .form-signin .form-control:focus { z-index: 2; } .form-signin input[type="email"] { margin-bottom: -1px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .form-signin input[type="password"] { margin-bottom: 10px; border-top-left-radius: 0; border-top-right-radius: 0; }
(3)cms/views.py
# cmd/views.py __author__ = 'derek' from flask import Blueprint,views,render_template bp = Blueprint("cms",__name__,url_prefix='/cms') @bp.route('/') def index(): return 'cms index' class LoginView(views.MethodView): def get(self): return render_template('cms/cms_login.html') def post(self): pass bp.add_url_rule('/login/',view_func=LoginView.as_view('login'))
浏览器访问:http://127.0.0.1:5000/cms/login/
2.2.cms后台登录功能完成
(1)cms/cmd_login.html
<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 %}
(2)cms/forms.py
# cmd/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='密码长度不够或超出')]) remember = IntegerField()
(4)cms/views.py
# cmd/views.py __author__ = 'derek' from flask import Blueprint,views,render_template,request,session from flask import url_for,redirect from .forms import LoginForm from .models import CMSUser bp = Blueprint("cms",__name__,url_prefix='/cms') @bp.route('/') def index(): return 'cms index' class LoginView(views.MethodView): def get(self,message=None): return render_template('cms/cms_login.html',message=message) def post(self): form = LoginForm(request.form) if form.validate(): email = form.email.data password = form.password.data remember = form.remember.data user = CMSUser.query.filter_by(email=email).first() if user and user.check_password(password): session['user_id'] = user.id if remember: # 31天后过期 session.permanent = True return redirect(url_for('cms.index')) else: return self.get(message='用户名或密码错误') else: #form.errors的错误信息格式,是一个字典,value是列表的形式 # {'email': ['请输入正确的邮箱格式'], 'password': ['密码长度不够或超出']} message = form.errors.popitem()[1][0] return self.get(message=message) bp.add_url_rule('/login/',view_func=LoginView.as_view('login'))
(4)config.py
import os SECRET_KEY = os.urandom(24)
2.3.cms后台登录限制
(1)config.py
CMS_USER_ID = 'abcdefg' #随便写一值,这样session更加安全
(2)修改LoginView
session[config.CMS_USER_ID] = user.id
(3)cms/decorators.py
# cms/decorators.py from flask import session,redirect,url_for from functools import wraps import config def login_required(func): @wraps(func) def inner(*args,**kwargs): if config.CMS_USER_ID in session: return func(*args,**kwargs) else: return redirect(url_for('cms.login')) return inner
(4)cms/cms_login.py
@bp.route('/') @login_required def index(): return 'cms index'
2.4.cms后台模板渲染完成
(1)static/cms/css/base.css
/* * Base structure */ /* Move down content because we have a fixed navbar that is 50px tall */ body { padding-top: 50px; overflow: hidden; } /* * Global add-ons */ .sub-header { padding-bottom: 10px; border-bottom: 1px solid #eee; } /* * Top navigation * Hide default border to remove 1px line. */ .navbar-fixed-top { border: 0; } /* * Sidebar */ /* Hide for mobile, show later */ .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; /* Scrollable contents if viewport is shorter than content. */ 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{ /*display: block;*/ 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; } /* * Placeholder dashboard ideas */ .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; } .top-box{ overflow: hidden; background: #ecedf0; padding: 10px 5px; }
(2)static/cms/js/base.js
/** * Created by Administrator on 2018/6/2. */ /** * Created by Administrator on 2016/12/17. */ $(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'); console.log(postManageLi); 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('fusers') >= 0){ var userManageLi = $('.user-manage'); userManageLi.addClass('unfold').siblings().removeClass('unfold'); }else if(url.indexOf('cusers') >= 0){ var cmsuserManageLi = $('.cmsuser-manage'); cmsuserManageLi.addClass('unfold').siblings().removeClass('unfold'); }else if(url.indexOf('croles') >= 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'); }else if(url.indexOf('banners')>=0){ var bannerManageLi=$('.banner-manage'); bannerManageLi.addClass('unfold').siblings().removeClass('unfold'); } });
(3)templates/common/_macros.html
创建一个宏
{#“-”表示去掉换行#}
{% macro static(filename) -%}
{{ url_for("static",filename=filename) }}
{%- endmacro %}
(4)templates/cms/cms_index.html
{% from "common/_macros.html" import static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>标题</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="{{ static('cms/css/base.css') }}"> <script src="{{ static('cms/js/base.js')}}"></script> </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="#">Zhang_derek论坛管理后台</a> </div> <div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li><a href="#">derek<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="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> </div> </div> </body> </html>
(5)app/cms/views.py
@bp.route('/') @login_required def index(): return render_template('cms/cms_index.html')
访问:http://127.0.0.1:5000/cms/