1、前臺輪播圖展示
根據權重查詢banners數據並傳輸,渲染到首頁界面:前臺藍圖文件:apps/front/views.py
# -*- encoding: utf-8 -*-
"""
@File : views.py
@Time : 2020/5/11 9:59
@Author : chen
前臺藍圖文件:apps/front/views.py
"""
# 前臺的藍圖文件 類視圖函數寫在這裏
from flask import Blueprint, render_template, views, make_response, request, session # make_response生成response對象,用於返回前端模板
# 導入圖像驗證碼生成文件
from utils.captcha import Captcha
# 圖形驗證碼image是二進制數據,需要轉換成字節流才能使用
from io import BytesIO
# 將圖形驗證碼保存到Redis restful輸出信息彈窗
from utils import redis_captcha, restful
# 驗證碼錶單信息驗證 登錄、註冊的Form表單信息收集
from .forms import SignupForm
from .forms import SigninForm
# 導入前臺用戶模型
from .models import Front_User
# 導入數據庫連接 db
from exts import db
# 確保URL安全的文件:utils/safe_url.py
from utils import safe_url
# 導入輪播圖模型BannerModel
from apps.cms.models import BannerModel
front_bp = Blueprint("front", __name__) # 前端不用前綴,直接在首頁顯示,front是藍圖,在front_signup.html調用生成圖形驗證碼時候需要用
# BBS的首頁界面路由
@front_bp.route("/")
def index():
banners = BannerModel.query.order_by(BannerModel.priority.desc()).limit(4) # 通過權重查詢,每頁顯示4條
return render_template("front/front_index.html", banners=banners) # 渲染到首頁界面,banners查詢數據傳輸到前臺界面
# 圖形驗證碼路由
@front_bp.route("/captcha/")
def graph_captcha():
try: # 異常處理
# 圖像驗證碼生成文件中返回兩個參數 text, image
text, image = Captcha.gene_graph_captcha() # 生成圖形驗證碼,image是二進制數據,需要轉換成字節流才能使用
print("發送的圖形驗證碼是:{}".format(text))
# 將圖形驗證碼保存到Redis數據庫中
redis_captcha.redis_set(text.lower(), text.lower()) # redis_set中需要傳參key和value,text沒有唯一對應的key,只能都傳參text
# BytesIO是生成的字節流
out = BytesIO()
image.save(out, 'png') # 把圖片image保存在字節流中,並指定爲png格式
# 文件流指針
out.seek(0) # 從字節流最初開始讀取
# 生成response對象,用於返回前端模板中
resp = make_response(out.read())
resp.content_type = 'image/png' # 指定數據類型
except:
return graph_captcha() # 沒有生成驗證碼就再調用一次
return resp # 返回對象
# 測試referrer的跳轉
@front_bp.route("/test/")
def test():
return render_template("front/front_test.html")
# 用戶註冊類視圖
class SingupView(views.MethodView):
def get(self):
# 圖像驗證碼生成文件中返回兩個參數 text, image
# text, image = Captcha.gene_graph_captcha()
# print(text) # 驗證碼
# print(image) # 圖形文件,圖形類<PIL.Image.Image image mode=RGBA size=100x30 at 0x1EFC9000C88>
# 從當前頁面跳轉過來就是None 從其他頁面跳轉過來輸出就是上一個頁面信息 referrer是頁面的跳轉
# print(request.referrer) # http://127.0.0.1:9999/test/
return_to = request.referrer
# 確保URL安全的文件:utils/safe_url.py
print(safe_url.is_safe_url(return_to)) # 判斷return_to是否來自站內,是否是安全url,防爬蟲
if return_to and return_to != request.url and safe_url.is_safe_url(return_to): # 跳轉的url不能是當前頁面,request.url是當前的url地址
return render_template("front/front_signup.html", return_to=return_to) # return_to渲染到前端界面
else:
return render_template("front/front_signup.html") # 如果沒獲取url,直接渲染註冊界面
# 驗證碼的form表單信息提交驗證
def post(self):
form = SignupForm(request.form) # 收集表單信息
# 表單驗證通過
if form.validate():
# 保存到數據庫
telephone = form.telephone.data
username = form.username.data
password = form.password1.data # forms表單信息
# 前臺用戶模型數據添加到數據庫
user = Front_User(telephone=telephone, username=username, password=password)
db.session.add(user)
db.session.commit() # 提交到數據庫
# 表單驗證通過,提交到數據庫成功
return restful.success()
else:
return restful.params_error(message=form.get_error()) # 表單信息驗證出錯
# 用戶登錄的類視圖
class SinginView(views.MethodView):
def get(self):
return_to = request.referrer # referrer是上一個url
if return_to and return_to != request.url and safe_url.is_safe_url(return_to): # 跳轉的url不能是當前頁面,判斷url是否安全
return render_template("front/front_signin.html", return_to=return_to) # return_to渲染到前端界面
else:
return render_template("front/front_signin.html") # 如果沒獲取url,直接渲染註冊界面
def post(self):
form = SigninForm(request.form) # 登錄界面的Form表單信息
if form.validate(): # 表單信息存在
# 收集form表單信息
telephone = form.telephone.data
password = form.password.data
remember = form.remember.data
user = Front_User.query.filter_by(telephone=telephone).first() # 通過手機號驗證該用戶是否存在數據庫
if user and user.check_password(password): # 判斷密碼和用戶是否正確
session['user_id'] = user.id # 用戶的id存儲到session中,用於登錄驗證
if remember: # 如果remember狀態是1
# session持久化
session.permanent = True
return restful.success() # 成功
else:
return restful.params_error(message="手機號或者密碼錯誤") # 密碼是、用戶不正確
else:
return restful.params_error(message=form.get_error()) # 表單信息不存在,輸出異常信息
# 綁定類視圖的路由
front_bp.add_url_rule("/signup/", view_func=SingupView.as_view("signup")) # "signup"視圖中不需要反斜線,決定了url_for的路由地址
front_bp.add_url_rule("/signin/", view_func=SinginView.as_view("signin")) # "signin"視圖中不需要反斜線
循環apps/front/views.py文件傳輸過來的banners數據,並渲染到界面上
前臺首頁界面:templates/front/front_index.html
{% extends 'front/front_base.html' %}
{% block title %}
首頁
{% endblock %}
<!-- 模板繼承 -->
{% block main_content %}
<!-- 居中樣式 -->
<div class="main-container">
<div class="lg-container">
<!-- bootstrop中複製來的輪播圖 -->
<div id="carousel-example-generic" class="carousel slide" data-ride="carousel">
<!-- 指令 -->
<ol class="carousel-indicators">
<li data-target="#carousel-example-generic" data-slide-to="0" class="active"></li>
<li data-target="#carousel-example-generic" data-slide-to="1"></li>
<li data-target="#carousel-example-generic" data-slide-to="2"></li>
</ol>
<!-- 輪播圖 -->
<div class="carousel-inner" role="listbox">
<!-- 循環apps/front/views.py文件傳輸的banners數據 -->
{% for banner in banners %}
<!-- 判斷是否第一次循環 -->
{% if loop.first %}
<div class="item active">
{% else %}
<div class="item">
{% endif %}
<!-- 輪播圖路徑,style="width: 300px;height: 300px"輪播圖大小 -->
<img src="{{ banner.image_url }}" alt="..." style="width: 300px;height: 300px">
<div class="carousel-caption">
</div>
</div>
{% endfor %}
<!-- -->
<!-- <div class="item">-->
<!-- <!– 輪播圖路徑 –>-->
<!-- <img src=".\static\common\images\1.png" alt="...">-->
<!-- <div class="carousel-caption">-->
<!-- ...-->
<!-- </div>-->
<!-- </div>-->
<!-- <!– 不同輪播圖 –>-->
<!-- <div class="item">-->
<!-- <!– 輪播圖路徑 –>-->
<!-- <img src=".\static\common\images\2.png" alt="...">-->
<!-- <div class="carousel-caption">-->
<!-- ...-->
<!-- </div>-->
<!-- </div>-->
<!-- ...-->
</div>
<!-- 輪播圖左右切換按鈕 -->
<a class="left carousel-control" href="#carousel-example-generic" role="button" data-slide="prev">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="right carousel-control" href="#carousel-example-generic" role="button" data-slide="next">
<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
<!-- bootstrop中複製來的輪播圖 代碼結束 -->
</div>
</div>
<!-- 居中樣式 -->
{% endblock %}
2、七牛雲上傳本地文件
注意七牛雲上傳文件的時候,上傳空間的地域需要選擇地區爲:華東
否則上傳文件不成功,報錯:incorrect region, please use up-z2.qiniup.com
導入七牛雲關聯樣式:static/common/lgqiniu.js
//'use strict';
var lgqiniu = {
'setUp': function(args) {
var domain = args['domain'];
var params = {
browse_button:args['browse_btn'],
runtimes: 'html5,flash,html4', //上傳模式,依次退化
max_file_size: '500mb', //文件最大允許的尺寸
dragdrop: false, //是否開啓拖拽上傳
chunk_size: '4mb', //分塊上傳時,每片的大小
uptoken_url: args['uptoken_url'], //ajax請求token的url
domain: domain, //圖片下載時候的域名
get_new_uptoken: false, //是否每次上傳文件都要從業務服務器獲取token
auto_start: true, //如果設置了true,只要選擇了圖片,就會自動上傳
unique_names: true,
multi_selection: false,
filters: {
mime_types :[
{title:'Image files',extensions: 'jpg,gif,png'},
{title:'Video files',extensions: 'flv,mpg,mpeg,avi,wmv,mov,asf,rm,rmvb,mkv,m4v,mp4'}
]
},
log_level: 5, //log級別
init: {
'FileUploaded': function(up,file,info) {
if(args['success']){
var success = args['success'];
file.name = domain + file.target_name;
success(up,file,info);
}
},
'Error': function(up,err,errTip) {
if(args['error']){
var error = args['error'];
error(up,err,errTip);
}
},
'UploadProgress': function (up,file) {
if(args['progress']){
args['progress'](up,file);
}
},
'FilesAdded': function (up,files) {
if(args['fileadded']){
args['fileadded'](up,files);
}
},
'UploadComplete': function () {
if(args['complete']){
args['complete']();
}
}
}
};
// 把args中的參數放到params中去
for(var key in args){
params[key] = args[key];
}
var uploader = Qiniu.uploader(params);
return uploader;
}
};
編寫獲取七牛雲的uptoken的接口文件:公共視圖文件:apps/common/views.py
# -*- encoding: utf-8 -*-
"""
@File : views.py
@Time : 2020/5/11 9:59
@Author : chen
公共視圖文件:apps/common/views.py
"""
# 導入手機驗證碼生成文件
from utils.send_telephone_msg import send_phone_msg
from utils import restful
from utils.captcha import Captcha
from flask import Blueprint, request, jsonify
from utils import redis_captcha # 圖形驗證碼存儲到redis數據庫中
# 導入form表單信息驗證客戶端sign2和服務端sign
from apps.common.forms import SMSCaptchaForm
# 導入七牛雲上傳文件的依賴庫
from qiniu import Auth, put_file, etag
import qiniu.config
common_bp = Blueprint("common", __name__, url_prefix='/c') # 視圖common,url前綴c,在
# 手機驗證碼生成文件,這部分是隻要調用當前路由請求,就會發送短信驗證碼,
# 需要利用sign = md5(timestamp+telephone+"q3423805gdflvbdfvhsdoa`#$%"),在front_signup.js文件中調用
# @common_bp.route("/sms_captcha/", methods=['POST'])
# def sms_captcha():
# telephone = request.form.get('telephone') # 表單信息收集
#
# if not telephone:
# return restful.params_error(message="請填寫手機號") # 手機信息不存在,輸出錯誤
#
# captcha = Captcha.gene_text(number=4) # 生成4位驗證碼,這裏生成的是驗證碼,要發送到手機端的,不能是圖形驗證碼
# # captcha = get_random_captcha(num=4): # 或者使用utils/random_captcha.py文件中的隨機生成驗證碼
#
# # 調用send_telephone_msg.py中send_phone_msg方法發送4位驗證碼到手機中
# if send_phone_msg(telephone, captcha) == 0: # 返回成功的狀態碼爲 0
# return restful.success()
# else:
# return restful.params_error("手機驗證碼發送失敗") # 手機驗證碼發送失敗
# 在front_signup.js文件中調用sign = md5()驗證表單信息.
@common_bp.route("/sms_captcha/", methods=['POST'])
def sms_captcha():
form = SMSCaptchaForm(request.form) # 收集form表單信息
if form.validate(): # 表單信息存在
# 接收數據
telephone = form.telephone.data
captcha = Captcha.gene_text(number=4) # 生成4位驗證碼,這裏生成的是驗證碼,要發送到手機端的,不能是圖形驗證碼
print("發送的手機驗證碼是:{}".format(captcha))
# 驗證發送成功狀態碼
if send_phone_msg(telephone, captcha) == 0: # 返回成功的狀態碼爲 0
redis_captcha.redis_set(telephone, captcha) # 將telephone對應的手機驗證碼保存在Redis數據庫中
return restful.success() # 返回成功信息提示框
else:
return restful.params_error("手機驗證碼發送失敗") # 手機驗證碼發送失敗
else:
return restful.params_error(message="參數錯誤")
# 創建七牛雲上傳文件路由,前後臺公有
@common_bp.route("/uptoken/") # 路由與static/cms/js/banners.js中上傳文件路由相同
def uptoken():
# 需要填寫你的 Access Key 和 Secret Key
access_key = 'F6TFlLqmX4Jxi_OJ86xLVCB8mQ5KRsyzCjGVWPEh'
secret_key = 'zhCb8cNSR-lifyVCZLPjH3GhD4_W7P5Sgbh9mHah'
# 構建鑑權對象
q = Auth(access_key, secret_key)
# 要上傳的空間
bucket_name = 'chenhao0406'
# 生成上傳 Token,可以指定過期時間等
token = q.upload_token(bucket_name) # 生成token,用於項目上傳使用
return jsonify({"uptoken": token}) # 鍵值對類型
前端中添加js的sdk:七牛云爲javascript提供了一個專門用來傳文件的接口:
後臺輪播圖管理:templates/cms/cms_banners.html
{% extends 'cms/cms_base.html' %}
{% block title %}
輪播圖管理
{% endblock %}
{% block page_title %}
{{ self.title() }}
{% endblock %}
{% block head %}
<!-- 七牛雲初始化文件,七牛云爲javascript提供了一個專門用來傳文件的接口 -->
<script src="https://cdn.staticfile.org/Plupload/2.1.1/moxie.js"></script>
<script src="https://cdn.staticfile.org/Plupload/2.1.1/plupload.dev.js"></script>
<script src="https://cdn.staticfile.org/qiniu-js-sdk/1.0.14-beta/qiniu.js"></script>
<script src="{{ url_for('static', filename='common/lgqiniu.js') }}"></script>
<script src="{{ url_for('static',filename='cms/js/banners.js') }}"></script>
<style>
.top-box button{
float: right;
}
</style>
{% endblock %}
{% block content %}
<div class="top-box">
<button class="btn btn-warning" data-toggle="modal" data-target="#banner-dialog">添加輪播圖</button>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>名稱</th>
<th>圖片鏈接</th>
<th>跳轉鏈接</th>
<th>優先級</th>
<th>創建時間</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 循環數據庫中banners信息,數據從視圖文件:apps/cms/views.py中傳輸渲染過來 -->
{% for banner in banners %}
<tr data-name="{{ banner.name }}" data-image="{{ banner.image_url }}" data-link="{{ banner.link_url }}"
data-priority="{{ banner.priority }}" data-id="{{ banner.id }}">
<td>{{ banner.name }}</td>
<!-- truncate(length=20)將鏈接的顯示長度控制在20字符 -->
<td><a href="{{ banner.image_url }}" target="_blank">{{ banner.image_url|truncate(length=20) }}</a></td>
<!-- 輪播圖跳轉鏈接顯示 權重、創建時間 -->
<td><a href="{{ banner.link_url }}" target="_blank">{{ banner.link_url }}</a></td>
<td>{{ banner.priority }}</td>
<td>{{ banner.create_time }}</td>
<td>
<button class="btn btn-default btn-xs edit-banner-btn">編輯</button>
<button class="btn btn-danger btn-xs delete-banner-btn">刪除</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- Modal -->
<div class="modal fade" id="banner-dialog" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span>
</button>
<h4 class="modal-title" id="myModalLabel">輪播圖</h4>
</div>
<div class="modal-body">
<form action="" class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label">名稱:</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="name" placeholder="輪播圖名稱">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">圖片:</label>
<div class="col-sm-7">
<input type="text" class="form-control" name="image_url" placeholder="輪播圖圖片">
</div>
<button class="btn btn-info col-sm-2" id="upload-btn">添加圖片</button>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">跳轉:</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="link_url" placeholder="跳轉鏈接">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">權重:</label>
<div class="col-sm-10">
<input type="number" class="form-control" name="priority" placeholder="優先級">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">關閉</button>
<button type="button" class="btn btn-primary" id="save-banner-btn">保存</button>
</div>
</div>
</div>
</div>
{% endblock %}
修改七牛雲初始化的參數:static/cms/js/banners.js文件,將七牛雲上傳域名改成自己的
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');
xhr.setRequestHeader("X-CSRFToken", csrftoken)
}
}
});
}
};
$(function () {
// 保存輪播圖按鈕
$("#save-banner-btn").click(function (event) {
event.preventDefault();
var self = $(this);
var dialog = $("#banner-dialog");
var nameInput = $("input[name='name']"); // 獲得表單輸入的信息
var imageInput = $("input[name='image_url']");
var linkInput = $("input[name='link_url']");
var priorityInput = $("input[name='priority']");
var name = nameInput.val();
var image_url = imageInput.val();
var link_url = linkInput.val();
var priority = priorityInput.val();
var submitType = self.attr('data-type'); // 獲取data-type的屬性用於判斷
var bannerId = self.attr("data-id");
if(!name || !image_url || !link_url || !priority){
lgalert.alertInfoToast('請輸入完整的輪播圖數據!');
return;
}
var url = '';
if(submitType == 'update'){ // 如果data-type的屬性是update
url = '/cms/ubanner/'; // 修改輪播圖選項update 跳轉到修改輪播圖路由
}else{
url = '/cms/abanner/'; // 添加輪播圖選項 add 跳轉到添加輪播圖路由
}
// form 發送 <form action="提交的地址" method="post">
lgajax.post({ // 方法是post,在視圖文件:apps/cms/views.py文件中添加輪播圖路由方法需要爲POST
"url": url,
'data':{ // Form表單名稱
'name':name,
'image_url': image_url,
'link_url': link_url,
'priority':priority,
'banner_id': bannerId
},
'success': function (data) {
dialog.modal("hide"); // 添加輪播圖Form表單界面隱藏
if(data['code'] == 200){
// 重新加載這個頁面
window.location.reload(); // 發送成功,頁面刷新
}else{
lgalert.alertInfo(data['message']); // 彈出異常信息
}
},
'fail': function () {
lgalert.alertNetworkError();
}
});
});
});
$(function () {
// 編輯輪播圖按鈕
$(".edit-banner-btn").click(function (event) {
var self = $(this);
var dialog = $("#banner-dialog");
dialog.modal("show");
var tr = self.parent().parent();
var name = tr.attr("data-name");
var image_url = tr.attr("data-image");
var link_url = tr.attr("data-link");
var priority = tr.attr("data-priority");
var nameInput = dialog.find("input[name='name']");
var imageInput = dialog.find("input[name='image_url']");
var linkInput = dialog.find("input[name='link_url']");
var priorityInput = dialog.find("input[name='priority']");
var saveBtn = dialog.find("#save-banner-btn");
nameInput.val(name);
imageInput.val(image_url);
linkInput.val(link_url);
priorityInput.val(priority);
saveBtn.attr("data-type",'update'); // data-type屬性update代表的是對輪播圖的修改更新,而不是添加保存
saveBtn.attr('data-id',tr.attr('data-id'));
});
});
$(function () {
// 刪除輪播圖選項按鈕
$(".delete-banner-btn").click(function (event) {
var self = $(this);
var tr = self.parent().parent();
var banner_id = tr.attr('data-id');
lgalert.alertConfirm({
"msg":"您確定要刪除這個輪播圖嗎?",
'confirmCallback': function () {
lgajax.post({
'url': '/cms/dbanner/',
'data':{
'banner_id': banner_id
},
'success': function (data) {
if(data['code'] == 200){
window.location.reload();
}else{
lgalert.alertInfo(data['message']);
}
}
})
}
});
});
});
$(function () {
lgqiniu.setUp({
'domain': 'http://qb0ruw0qs.bkt.clouddn.com/', // 修改成自己賬戶的七牛雲域名
'browse_btn': 'upload-btn',
'uptoken_url': '/c/uptoken/', // 七牛雲上傳文件路由名稱/uptoken/,與公共視圖文件:apps/common/views.py中命名相同
'success': function (up,file,info) {
var imageInput = $("input[name='image_url']");
imageInput.val(file.name);
}
});
});
3、板塊管理
創建板塊管理中的文章模型類,再映射到數據庫中:後臺模型文件:apps/cms/models.py
# -*- encoding: utf-8 -*-
"""
@File : models.py
@Time : 2020/5/11 10:00
@Author : chen
後臺模型文件:apps/cms/models.py
"""
# 定義後端用戶模型
from exts import db # 數據庫
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash # 導入密碼加密,解密方法的庫
# 權限定義,不是模型,沒有繼承db.Model
class CMSPersmission(object):
# 255 二進制表示所有的權限
ALL_PERMISSION = 0b11111111 # 每一位數代表一個權限,共7個權限,8位1個字節
# 訪問權限
VISITOR = 0b00000001
# 管理帖子
POSTER = 0b00000010
# 管理評論
COMMENTER = 0b00000100
# 管理板塊
BOARDER = 0b00001000
# 管理後臺用戶
CMSUSER = 0b00010000
# 管理前臺用戶
FRONTUSER = 0b00100000
# 管理管理員用戶
ADMINER = 0b01000000
# 權限與角色是多對多的關係,創建他們的中間表
cms_role_user = db.Table(
"cms_role_user",
db.Column("cms_role_id", db.Integer, db.ForeignKey('cms_role.id'), primary_key=True),
db.Column("cms_user_id", db.Integer, db.ForeignKey('cms_user.id'), primary_key=True),
)
# 角色模型定義 繼承了db.Model
class CMSRole(db.Model):
__tablename__ = 'cms_role'
id = db.Column(db.Integer, primary_key=True, autoincrement=True) # 主鍵 自增
name = db.Column(db.String(50), nullable=False) # 非空
desc = db.Column(db.String(250), nullable=False) # 非空
creat_time = db.Column(db.DateTime, default=datetime.now)
permission = db.Column(db.Integer, default=CMSPersmission.VISITOR) # 默認先給遊客權限
# 反向查詢屬性,關聯中間表secondary=cms_role_user,對應了CMS_User模型,建立模型聯繫,不映射到數據庫中
users = db.relationship('CMS_User', secondary=cms_role_user, backref="roles") # roles是CMS_User的外鍵
# 後臺用戶模型定義
class CMS_User(db.Model):
__tablename__ = 'cms_user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True) # 主鍵 自增
username = db.Column(db.String(150), nullable=False) # 非空
# password = db.Column(db.String(150), nullable=False)
_password = db.Column(db.String(150), nullable=False) # 密碼加密操作修改字段
email = db.Column(db.String(50), nullable=False, unique=True) # 非空、唯一
join_time = db.Column(db.DateTime, default=datetime.now) # 默認當前時間
# 修改密碼加密操作中的字段,在manage.py映射數據庫時候,使用字段還是保持相同
def __init__(self, username, password, email):
self.username = username
self.password = password # 調用該方法 返回下面的self._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)
# 用於驗證後臺登錄密碼是否和數據庫一致,raw_password是後臺登錄輸入的密碼
def check_password(self, raw_password):
result = check_password_hash(self.password, raw_password) # 相當於用相同的hash加密算法加密raw_password,檢測與數據庫中是否一致
return result
# 封裝用戶的權限
@property
def permission(self):
if not self.roles: # 反向查詢屬性,backref="roles",
return 0 # 沒有任何權限
# 所有權限
all_permissions = 0
for role in self.roles: # 循環調用所有角色
permissions = role.permission # 將這個角色的權限都取出來 role.permission代表CMSRole中的屬性
all_permissions |= permissions # 當前這個角色的權限都在all_permissions
return all_permissions
# 判斷用戶所具有的權限
def has_permissions(self, permission):
all_permissions = self.permission # 調用permission(self)方法
# 若所有權限0b11111111 & 用戶權限 等於 本身,則代表具有該權限
result = all_permissions & permission == permission
# print(result)
return result
# 判斷是否是開發人員
@property
def is_developer(self):
return self.has_permissions(CMSPersmission.ALL_PERMISSION) # 調用has_permissions方法並傳入所有權限
# 輪播圖的模型創建
class BannerModel(db.Model):
__tablename__ = 'banner'
id = db.Column(db.Integer, primary_key=True, autoincrement=True) # 主鍵 自增
name = db.Column(db.String(250), nullable=False) # 非空
# 圖片鏈接
image_url = db.Column(db.String(250), nullable=False) # 輪播圖的鏈接資源
# 跳轉鏈接
link_url = db.Column(db.String(50), nullable=False)
priority = db.Column(db.Integer, default=0) # 權重選項
create_time = db.Column(db.DateTime, default=datetime.now) # 創建時間
# 刪除標誌字段 0代表刪除 1代表未刪除
is_delete = db.Column(db.Integer, default=1)
# 板塊管理模型創建
class BoardModel(db.Model):
__tablename__ = 'cms_board'
id = db.Column(db.Integer, primary_key=True, autoincrement=True) # 主鍵 自增
name = db.Column(db.String(250), nullable=False) # 非空
create_time = db.Column(db.DateTime, default=datetime.now) # 創建時間
導入manage.py文件中才能映射到數據庫,否則不成功:映射模型到數據庫中文件: manage.py
# -*- encoding: utf-8 -*-
"""
@File : manage.py
@Time : 2020/5/10 17:36
@Author : chen
映射模型到數據庫中文件: manage.py
"""
from flask_script import Manager
from bbs import app # 需要將當前文件夾設置爲當前根目錄,纔不會報錯
from flask_migrate import Migrate, MigrateCommand
from exts import db
# 導入後臺模型 才能映射到數據庫 導入後端的模型
from apps.cms.models import CMS_User
# 導入後臺模型 才能映射到數據庫 ,導入輪播圖和文章的管理模塊
from apps.cms.models import BannerModel, BoardModel
# 導入後臺角色模型,映射到數據庫 CMSPersmission角色權限定義類
from apps.cms.models import CMSRole, CMSPersmission
# 導入前臺模型 才能映射到數據庫
from apps.front.models import Front_User
manage = Manager(app)
Migrate(app, db)
manage.add_command('db', MigrateCommand)
# 命令行添加後臺用戶
@manage.option('-u', '--username', dest='username')
@manage.option('-p', '--password', dest='password')
@manage.option('-e', '--email', dest='email')
def create_cms_user(username, password, email):
user = CMS_User(username=username, password=password, email=email)
# 添加映射到數據庫,提交至數據庫
db.session.add(user)
db.session.commit()
print("cms後臺用戶添加成功")
# 命令行添加前臺用戶
@manage.option('-t', '--telephone', dest='telephone')
@manage.option('-u', '--username', dest='username')
@manage.option('-p', '--password', dest='password')
def create_front_user(telephone, username, password):
user = Front_User(telephone=telephone, username=username, password=password)
# 添加映射到數據庫,提交至數據庫
db.session.add(user)
db.session.commit()
print("front前臺用戶添加成功")
# 添加角色 不傳參用command
@manage.command
def create_role():
# 訪問者
visitor = CMSRole(name="訪問者", desc="只能查看數據,不能修改數據")
visitor.permission = CMSPersmission.VISITOR # 權限
# 運營人員
operator = CMSRole(name="運營人員", desc="管理評論、帖子、管理前臺用戶")
# 權限或運算,代表包含有運算中的所有權限 二進制的運算 001|010=011
operator.permission = CMSPersmission.VISITOR | CMSPersmission.POSTER | CMSPersmission.CMSUSER | \
CMSPersmission.COMMENTER | CMSPersmission.FRONTUSER
# 管理員
admin = CMSRole(name="管理員", desc="擁有本系統大部分權限")
admin.permission = CMSPersmission.VISITOR | CMSPersmission.POSTER | CMSPersmission.CMSUSER | \
CMSPersmission.COMMENTER | CMSPersmission.FRONTUSER | CMSPersmission.BOARDER
# 開發人員
developer = CMSRole(name="開發人員", desc="擁有本系統所有權限")
developer.permission = CMSPersmission.ALL_PERMISSION
# 提交數據庫 添加身份字段到數據庫中的表,
db.session.add_all([visitor, operator, admin, developer])
db.session.commit()
return "創建角色成功"
# 測試用戶權限
@manage.command
def test_permission():
# user = CMS_User.query.first() # 查詢第一個用戶,當時創建的用戶還沒有關聯權限,所以應該是沒有權限
user = CMS_User.query.get(3)
print(user) # 顯示用戶信息
if user.has_permissions(CMSPersmission.VISITOR): # has_permissions方法判定是否具有該權限
print("這個用戶有訪問者的權限!")
else:
print("這個用戶有訪問者的權限!")
# 添加用戶到角色裏面
@manage.option("-e", "--email", dest="email")
@manage.option("-n", "--name", dest="name")
def add_user_to_role(email, name):
user = CMS_User.query.filter_by(email=email).first() # 通過郵箱查詢用戶
if user:
role = CMSRole.query.filter_by(name=name).first() # 郵箱存在的前提下,通過name查詢角色
if role:
role.users.append(user) # 將用戶添加到角色中,list類型數據,role.users是CMSRole中的外鍵
db.session.commit() # 映射到數據庫
print("用戶添加到角色成功")
else:
print("該角色不存在")
else:
print("郵箱不存在")
if __name__ == '__main__':
manage.run()
映射到數據庫中:
板塊管理頁面
增刪改查功能實現
先創建板塊的Form表單:forms表單信息:apps/cms/forms.py
# -*- encoding: utf-8 -*-
"""
@File : forms.py
@Time : 2020/5/11 10:00
@Author : chen
forms表單信息:apps/cms/forms.py
"""
# forms表單信息
from wtforms import Form, StringField, IntegerField, ValidationError
from wtforms.validators import Email, InputRequired, Length, EqualTo, URL # EqualTo驗證新密碼是否相同,URL驗證
from utils.redis_captcha import redis_get # 導入驗證碼模塊
# 創父類form表單,用於輸出錯誤信息
class BaseForm(Form):
def get_error(self):
message = self.errors.popitem()[1][0] # 錯誤信息的收集,字典類型數據信息提取
return message
# 登錄頁面中的Form表單 繼承父類form
class LoginForm(BaseForm):
email = StringField(validators=[Email(message="請輸入正確的郵箱"), InputRequired(message="請輸入郵箱")])
password = StringField(validators=[Length(3, 15, message='請輸入正確長度的密碼')]) # 長度可以先設置短的,方便項目測試
remember = IntegerField() # 記住cookie操作 賦值爲0或1
# 修改密碼頁面中的form表單信息 繼承父類form
class ResetPwdForm(BaseForm):
oldpwd = StringField(validators=[Length(3, 15, message="密碼長度有誤")])
newpwd = StringField(validators=[Length(3, 15, message="密碼長度有誤")])
newpwd2 = StringField(validators=[EqualTo("newpwd", message="兩次輸入密碼不一致")])
# 定義設置郵箱的表單信息,進行提交時候使用
class ResetEmailForm(BaseForm):
email = StringField(validators=[Email(message="請輸入正確格式的郵箱")]) # 名稱email與cms_resetemail.html中的要相同
captcha = StringField(
validators=[Length(min=4, max=4, message="請輸入正確長度的驗證碼")]) # 名稱captcha與cms_resetemail.html中的要相同
# 驗證redis中的字段與數據庫中的字段是否相同
def validate_captcha(self, field): # 方法命名規則是:validate_字段名()
# 表單提交上來的驗證碼
email = self.email.data
captcha = self.captcha.data
# 取redis中保存的驗證碼 第一個redis_captcha是新對象,第二個redis_captcha是redis_captcha.py文件
redis_captcha = redis_get(email)
if not redis_captcha or captcha.lower() != redis_captcha.lower(): # 不區分大小寫
raise ValidationError('郵箱驗證碼錯誤')
# 定義 添加輪播圖 的表單信息
class AddBannerForm(BaseForm):
# Form表單名稱根據static/cms/js/banners.js中的ajax.post發送的data中
name = StringField(validators=[InputRequired(message="請輸入輪播圖名稱")])
image_url = StringField(validators=[InputRequired(message="請輸入輪播圖片鏈接"), URL(message="圖片鏈接有誤")])
link_url = StringField(validators=[InputRequired(message="請輸入輪播圖上跳轉鏈接"), URL(message="跳轉鏈接有誤")])
priority = IntegerField(validators=[InputRequired(message="請輸入輪播圖優先級")])
# 定義 修改輪播圖 的表單信息
class UpdateBannerForm(AddBannerForm): # 繼承AddBannerForm,收集表單信息一樣,只多出來一個查詢字段banner_id
# 根據banner_id查詢 修改 輪播圖
banner_id = IntegerField(validators=[InputRequired(message="輪播圖不存在")])
# 定義 增加板塊管理 的表單信息
class AddBoardsForm(BaseForm):
# 板塊名稱
name = StringField(validators=[InputRequired(message="板塊名稱不存在")])
# 編輯 板塊管理名稱
class UpdateBoardsForm(AddBoardsForm):
# 修改編輯板塊名稱的時候需要使用board_id進行查詢,再修改
board_id = IntegerField(validators=[InputRequired(message="請輸入板塊ID")])
創建路由,並收集表單信息,查詢數據庫中信息再進行增刪改查操作:視圖文件:apps/cms/views.py文件
# -*- encoding: utf-8 -*-
"""
@File : views.py
@Time : 2020/5/11 9:59
@Author : chen
視圖文件:apps/cms/views.py文件
"""
# 藍圖文件:實現模塊化應用,應用可以分解成一系列的藍圖 後端的類視圖函數寫在這個文件
from flask import (
request, redirect, url_for, # 頁面跳轉redirect request請求收集
Blueprint, render_template, views, session, # 定義類視圖,顯示模板文件
jsonify, g # jsonify強制轉換成json數據
)
from exts import db, mail # 數據庫中更新密碼、郵箱等使用
from apps.cms.forms import (
LoginForm, ResetPwdForm, # ResetPwdForm修改密碼的form信息
ResetEmailForm, # 導入forms.py文件中的郵箱驗證的表單信息類
AddBannerForm, # 導入 添加輪播圖 的表單信息
UpdateBannerForm, # 導入 更新輪播圖 的表單信息
AddBoardsForm, # 導入 增加板塊管理 的表單信息
UpdateBoardsForm, # 導入 編輯板塊管理 的表單信息
)
from apps.cms.models import (
CMS_User, # 後臺用戶模型
CMSPersmission, # CMSPersmission驗證用戶不同模塊權限
CMSRole, # 用戶角色模型
BannerModel, # 導入 輪播圖模型BannerModel
BoardModel, # 導入 板塊管理模型
)
from .decorators import permission_required # 傳參裝飾器驗證用戶不同模塊權限
# 導入裝飾器:判斷當前界面是否是登錄界面,不是就將url重定向到登錄界面,一般不用,使用的主要是鉤子函數
from .decorators import login_required
# 導入restful.py中的訪問網頁狀態碼的函數 redis_captcha:redis存儲、提取、刪除驗證碼功能
from utils import restful, random_captcha, redis_captcha # 隨機生成驗證碼函數random_captcha()
# 導入flask-mail中的Message
from flask_mail import Message
cms_bp = Blueprint("cms", __name__, url_prefix='/cms/') # URL前綴url_prefix
# 鉤子函數是在cms_bp創建之後才創建的,順序在cms_bp創建之後
from .hooks import before_request
@cms_bp.route("/") # 後臺界面
# @login_required # 裝飾器判定當前界面是否是登錄界面,但是需要每個路由函數都要加該裝飾器,比較麻煩,推薦使用鉤子函數
def index():
# return "cms index:後端類視圖文件"
return render_template('cms/cms_index.html') # 登陸之後進入CMS後臺管理界面
# 用戶註銷登錄
@cms_bp.route("/logout/") # 需要關聯到cms/cms_index.html中的註銷屬性
def logout():
# session清除user_id
del session['user_id']
# 重定向到登錄界面
return redirect(url_for('cms.login')) # 重定向(redirec)爲把url變爲重定向的url
# 定義個人中心的路由
@cms_bp.route("/profile/")
def profile():
return render_template("cms/cms_profile.html") # 模板渲染(render_template)則不會改變url,模板渲染是用模板來渲染請求的url
# 定義類視圖,顯示模板文件 用戶登錄功能實現
class LoginView(views.MethodView):
def get(self, message=None): # message=None時候不傳輸信息到cms_login.html頁面
return render_template("cms/cms_login.html", message=message) # 針對post方法中同樣要返回到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 = CMS_User.query.filter_by(email=email).first() # 郵箱唯一,用於查詢驗證用戶
if user and user.check_password(password): # 驗證用戶和密碼是否都正確
session['user_id'] = user.id # 查詢到用戶數據時,保存session的id到瀏覽器
# session['user_name'] = user.username # 將數據庫中的user.username保存到session中,在hooks.py中判斷
# session['user_email'] = user.email # 將數據庫中的email保存到session中,方便html調用信息
# session['user_join_time'] = user.join_time # 將數據庫中的join_time保存到session中,方便html調用信息
if remember: # 如果用戶點擊了remember選擇,在瀏覽器中進行數據持久化
session.permanent = True # 數據持久化,默認31天,需要設置session_key在config.py中
# 登錄成功,跳轉到後臺首頁
return redirect(url_for('cms.index')) # 在藍圖中必須加cms 跳轉到index方法
else:
# return "郵箱或密碼錯誤" # 登錄出錯,返回結果
# return render_template("cms/cms_login.html", message="郵箱或密碼錯誤") # 登錄出錯,返回結果渲染到cms_login.html頁面
return self.get(message="郵箱或密碼錯誤") # 傳參到get方法中,多加一個傳輸錯誤信息的參數到方法中
else:
# print(login_form.errors) # forms.py中的錯誤信息 字典類型數據
# print(login_form.errors.popitem()) # forms.py中的錯誤信息 元祖類型數據
# return "表單驗證錯誤" # 錯誤信息需要渲染到cms_login.html頁面
# return self.get(message=login_form.errors.popitem()[1][0]) # 字典類型數據信息提取
return self.get(message=login_form.get_error()) # login_form是收集到的表單信息,信息提取放置到forms.py的父類中實現
# 修改密碼的類視圖驗證
class ResetPwd(views.MethodView):
def get(self):
return render_template('cms/cms_resetpwd.html') # 模板渲染到cms_resetpwd.html
# post提交密碼修改
def post(self):
# 先審查舊密碼是否與數據庫中的信息相同
form = ResetPwdForm(request.form)
if form.validate():
oldpwd = form.oldpwd.data
newpwd = form.newpwd.data
# 對象
user = g.cms_user
# 將用戶輸入的密碼進行加密檢測是否與數據庫中的相同
if user.check_password(oldpwd):
# 更新我的密碼 將新密碼賦值,此時的新密碼已經經過驗證二次密碼是否一致
user.password = newpwd # user.password已經調用了models.py中的 @property裝飾器進行密碼加密
# 數據庫更新
db.session.commit()
# return jsonify({"code": 400, "message": "密碼修改成功"}) # 代碼改寫爲下面
return restful.success("密碼修改成功") # 調用restful.py中定義的訪問網頁成功的函數
else:
# 當前用戶輸入的舊密碼與數據庫中的不符
# return jsonify({"code": 400, "message": "舊密碼輸入錯誤"})
return restful.params_error(message="舊密碼輸入錯誤") # 參數錯誤
else:
# ajax 需要返回一個json類型的數據
# message = form.errors.popitem()[1][0] # 收集錯誤信息
# return jsonify({"code": 400, "message": message}) # 將數據轉換成json類型
return restful.params_error(message=form.get_error()) # 參數錯誤,信息的收集在forms.py的父類函數中實現 form是收集到的信息
# 定義修改郵箱的類視圖 驗證
class ResetEmail(views.MethodView):
def get(self):
return render_template("cms/cms_resetemail.html") # 返回到修改郵箱頁面url
def post(self):
form = ResetEmailForm(request.form) # 接收郵箱驗證的form表單信息
if form.validate(): # 驗證表單信息是否通過
email = form.email.data # 獲取form表單中填寫的郵箱地址
# 查詢數據庫
# CMS_User.query.filter_by(email=email).first()
# CMS_User.query.filter(CMS_User.email == email).first()
g.cms_user.email = email # 數據庫中的查詢在apps/cms/hooks.py文件中確定了該用戶的數據庫信息,用全局對象g.cms_user修改郵箱
db.session.commit()
return restful.success() # 郵箱修改成功
else:
return restful.params_error(form.get_error()) # form是這個類中的所有表單信息
# 發送測試郵件進行驗證
@cms_bp.route("/send_email/")
def send_mail():
message = Message('郵件發送', recipients=['[email protected]'], body='測試郵件發送') # 主題:郵件發送;收件人:recipients;郵件內容:測試郵件發送
mail.send(message) # 發送郵件
return "郵件已發送"
# 郵件發送
class EmailCaptcha(views.MethodView):
def get(self): # 根據resetemail.js中的ajax方法來寫函數,不需要post請求
email = request.args.get('email') # 查詢email參數是否存在
if not email:
return restful.params_error('請傳遞郵箱參數')
# 發送郵件,內容爲一個驗證碼:4、6位數字英文組合
captcha = random_captcha.get_random_captcha(4) # 生成4位驗證碼
message = Message('BBS論壇郵箱驗證碼', recipients=[email], body='您的驗證碼是:%s' % captcha)
# 異常處理
try:
mail.send(message)
except:
return restful.server_error(message="服務器錯誤,郵件驗證碼未發送!") # 發送異常,服務器錯誤
# 驗證碼保存,一般有時效性,且頻繁請求變化,所以保存在Redis中
redis_captcha.redis_set(key=email, value=captcha) # redis中都是鍵值對類型,存儲驗證碼
return restful.success("郵件驗證碼發送成功!")
# 輪播圖管理路由
@cms_bp.route("/banners/")
def banners():
# 通過模型中定義的權重priority的倒敘來排序
banners = BannerModel.query.order_by(BannerModel.priority.desc()).all()
return render_template("cms/cms_banners.html", banners=banners) # 傳輸banners數據到cms_banners.html界面渲染
# 添加輪播圖功能路由,且方法需要與static/cms/js/banners.js中綁定的方法POST相同
@cms_bp.route("/abanner/", methods=['POST'])
def abanner():
form = AddBannerForm(request.form) # 接收添加輪播圖的form表單信息
if form.validate():
name = form.name.data
image_url = form.image_url.data
link_url = form.link_url.data
priority = form.priority.data
banner = BannerModel(name=name, image_url=image_url, link_url=link_url, priority=priority) # 輪播圖模型
db.session.add(banner) # 提交數據庫
db.session.commit()
return restful.success() # 輪播圖信息提交成功
else:
return restful.params_error(message=form.get_error()) # 表單信息錯誤
# 修改 輪播圖 路由,方法與static/cms/js/banners.js中綁定的方法POST相同
@cms_bp.route("/ubanner/", methods=['POST'])
def ubanner():
# 修改根據banner_id查詢再修改
form = UpdateBannerForm(request.form) # 表單信息UpdateBannerForm中的request
if form.validate(): # 先查詢頁面表單信息是否存在
banner_id = form.banner_id.data # 收集用戶輸入的表單信息
name = form.name.data
image_url = form.image_url.data
link_url = form.link_url.data
priority = form.priority.data
banner = BannerModel.query.get(banner_id) # 通過輪播圖的模型BannerModel的banner_id查詢數據庫中輪播圖對象
if banner: # 再查詢數據庫對象數據是否存在
banner.name = name # 將UpdateBannerForm中收集到的form信息命名給數據庫中的banner對象
banner.image_url = image_url
banner.link_url = link_url
banner.priority = priority
db.session.commit() # 數據庫信息直接提交修改即可,不用添加新的對象
return restful.success()
else:
return restful.params_error(message=form.get_error()) # 表單信息錯誤
# 刪除 輪播圖路由,路由命名與banners.js綁定
@cms_bp.route("/dbanner/", methods=['POST'])
def dbanner():
'''
request.form.get("key", type=str, default=None) 獲取表單數據
request.args.get("key") 獲取get請求參數
request.values.get("key") 獲取所有參數
'''
# 修改根據banner_id查詢再修改,獲取post請求參數 get請求方式使用request.args.get()
banner_id = request.form.get('banner_id') # 獲取表單數據,這裏沒有單獨創建刪除的Form表單,使用之前創建的
if not banner_id:
return restful.params_error(message="輪播圖不存在")
banner = BannerModel.query.get(banner_id) # 根據banner_id查詢數據庫
if banner:
db.session.delete(banner) # 刪除該banner
db.session.commit()
return restful.success() # 返回成功
else:
return restful.params_error("輪播圖不存在") # 根據banner_id查詢數據庫信息不存在
# 帖子管理路由 ,需要和cms_base.js中命名的相同纔可以
@cms_bp.route("/posts/")
@permission_required(CMSPersmission.POSTER) # 傳參裝飾器驗證不同用戶不同模塊權限
def posts():
return render_template("cms/cms_posts.html")
# 評論管理路由
@cms_bp.route("/comments/")
@permission_required(CMSPersmission.COMMENTER) # 傳參裝飾器驗證不同用戶不同模塊權限
def comments():
return render_template("cms/cms_comments.html")
# 板塊管理路由
@cms_bp.route("/boards/")
@permission_required(CMSPersmission.BOARDER) # 傳參裝飾器驗證不同用戶不同模塊權限
def boards():
boards = BoardModel.query.all() # 數據庫查詢所有板塊名稱
return render_template("cms/cms_boards.html", boards=boards) # 數據渲染到cms_boards.html
# 增加 板塊管理名稱 路由,與static/cms/js/banners.js中綁定的方法、路由要相同
@cms_bp.route("/aboard/", methods=['POST'])
@permission_required(CMSPersmission.BOARDER) # 傳參裝飾器驗證不同用戶不同模塊權限
def aboards():
form = AddBoardsForm(request.form) # 表單信息傳輸過來,方便修改調用
if form.validate():
name = form.name.data # 表單信息收集
board = BoardModel(name=name) # 添加信息到板塊模型中
db.session.add(board)
db.session.commit() # 提交數據庫
return restful.success() # 數據庫添加成功
else:
return restful.params_error(message=form.get_error()) # 表單信息錯誤
# 編輯 板塊管理名稱 路由,與static/cms/js/banners.js中綁定的方法、路由要相同
@cms_bp.route("/uboard/", methods=['POST'])
@permission_required(CMSPersmission.BOARDER) # 傳參裝飾器驗證不同用戶不同模塊權限
def uboards():
form = UpdateBoardsForm(request.form) # 表單信息傳輸過來,方便修改調用
if form.validate():
board_id = form.board_id.data # 表單信息收集
name = form.name.data
board = BoardModel.query.get(board_id) # 根據表單中提交的board_id查詢數據庫中對象信息
if board:
board.name = name # 表單中提交的name命名給數據庫中對象的名字
db.session.commit() # 修改數據後提交數據庫
return restful.success() # 數據庫修改成功
else:
return restful.params_error(message="沒有這個分類板塊") # 數據庫中對象信息不存在
else:
return restful.params_error(message=form.get_error()) # 表單信息錯誤
# 刪除 板塊管理名稱 路由,與static/cms/js/banners.js中綁定的方法、路由要相同
@cms_bp.route("/dboard/", methods=['POST'])
@permission_required(CMSPersmission.BOARDER) # 傳參裝飾器驗證不同用戶不同模塊權限
def dboards():
board_id = request.form.get('board_id') # 查詢表單信息中的board_id,這裏沒有單獨創建刪除的Form表單,使用之前創建的
if not board_id:
return restful.params_error(message="分類板塊不存在") # 表單信息不存在
board = BoardModel.query.get(board_id) # 根據表單中提交的board_id查詢數據庫中對象信息,注意.get
if not board:
return restful.params_error(message="分類板塊不存在") # 數據庫中對象信息不存在
db.session.delete(board) # 刪除數據庫中的信息
db.session.commit() # 提交數據庫修改
return restful.success() # 刪除成功
# 前臺用戶管理路由
@cms_bp.route("/fusers/")
@permission_required(CMSPersmission.FRONTUSER) # 傳參裝飾器驗證不同用戶不同模塊權限
def fuser():
return render_template("cms/cms_fuser.html")
# 後用戶管理路由
@cms_bp.route("/cusers/")
@permission_required(CMSPersmission.CMSUSER) # 傳參裝飾器驗證不同用戶不同模塊權限
def cuser():
return render_template("cms/cms_cuser.html")
# 添加登錄路由
cms_bp.add_url_rule("/login/", view_func=LoginView.as_view('login')) # view_func 命名操作名字,"/login/"路由地址
# 類視圖函數添加綁定路由 注意類視圖需要修改ResetPwd.as_view('resetpwd')
cms_bp.add_url_rule("/resetpwd/", view_func=ResetPwd.as_view('resetpwd')) # view_func 命名操作名字,/resetpwd/路由地址
# 添加修改郵箱的類視圖路由綁定,路由的命名和cms_base.js中的命名要相同,否則不關聯,url=/resetemail/必須要和resetemail.js中的ajax綁定的路由相同
cms_bp.add_url_rule("/resetemail/", view_func=ResetEmail.as_view('resetemail'))
# 綁定路由,路由的命名和cms_base.js中的命名要相同,必須要和resetemail.js中的ajax綁定的路由相同
cms_bp.add_url_rule("/email_captcha/", view_func=EmailCaptcha.as_view('email_captcha'))
路由創建命名、關聯按鈕實現,方法名稱POST確定綁定等:static/cms/js/banners.js文件
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');
xhr.setRequestHeader("X-CSRFToken", csrftoken)
}
}
});
}
};
$(function () {
// 添加按鈕
$("#add-board-btn").click(function (event) {
event.preventDefault();
lgalert.alertOneInput({
'text':'請輸入板塊名稱!',
'placeholder': '板塊名稱',
'confirmCallback': function (inputValue) {
lgajax.post({
'url': '/cms/aboard/', // 路由名稱與視圖文件:apps/cms/views.py文件中定義要一樣
'data': {
'name': inputValue
},
'success': function (data) {
if(data['code'] == 200){
window.location.reload();
}else{
lgalert.alertInfo(data['message']);
}
}
});
}
});
});
});
$(function () {
// 編輯按鈕
$(".edit-board-btn").click(function () {
var self = $(this);
var tr = self.parent().parent();
var name = tr.attr('data-name'); // 查詢添加板塊名稱的屬性,用於編輯板塊名稱的時候渲染到placeholder
var board_id = tr.attr("data-id");
lgalert.alertOneInput({
'text': '請輸入新的板塊名稱!',
'placeholder': name, // 編輯修改的時候,顯示最早輸入時候的名稱
'confirmCallback': function (inputValue) {
lgajax.post({
'url': '/cms/uboard/', // 路由名稱與視圖文件:apps/cms/views.py文件中定義要一樣
'data': {
'board_id': board_id, // 'board_id'是表單信息中輸入的name,value值爲board_id
'name': inputValue
},
'success': function (data) {
if(data['code'] == 200){
window.location.reload();
}else{
lgalert.alertInfo(data['message']);
}
}
});
}
});
});
});
$(function () {
// 刪除按鈕
$(".delete-board-btn").click(function (event) {
var self = $(this);
var tr = self.parent().parent();
var board_id = tr.attr('data-id');
lgalert.alertConfirm({
"msg":"您確定要刪除這個板塊嗎?",
'confirmCallback': function () {
lgajax.post({
'url': '/cms/dboard/', // 路由名稱與視圖文件:apps/cms/views.py文件中定義要一樣
'data':{
// form input name value
'board_id': board_id // 'board_id'是表單信息中輸入的name,value值爲board_id
},
'success': function (data) {
if(data['code'] == 200){
window.location.reload();
}else{
lgalert.alertInfo(data['message']);
}
}
})
}
});
});
});
模板繼承文件修改路由綁定:templates/cms/cms_base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- 在頭文件中接收csrf信息 -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<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>
<!-- 關聯本地的cms_base.css樣式 後臺管理界面CMS的樣式 -->
<link rel="stylesheet" href="{{ url_for('static', filename='cms/css/cms_base.css') }}">
<!-- 關聯本地的cms_base.js樣式 後臺管理界面CMS的樣式 -->
<script src="{{ url_for('static', filename='cms/js/cms_base.js') }}"></script>
<!-- 提示框的靜態資源文件 -->
<link rel="stylesheet" href="{{ url_for('static', filename='common/sweetalert/sweetalert.css') }}">
<!-- 關聯提示框的js樣式 -->
<script src="{{ url_for('static', filename='common/sweetalert/lgalert.js') }}"></script>
<script src="{{ url_for('static', filename='common/sweetalert/sweetalert.min.js') }}"></script>
<!-- 預留空間,給之後的html文件進行修改調整 -->
{% 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">
<!-- 從數據庫中調用用戶名,g對象全局調用g.cms_user對象 .username是該對象的一個字段屬性 -->
<li><a href="#">{{ g.cms_user.username }}</a></li>
<!-- 用戶註銷,關聯到views.py中的@cms_bp.route("/logout/")路由,重定向到該路由 -->
<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">
<!-- url重定向到/cms/profile/下 路由在views.py中定義了 -->
<li><a href="{{ url_for('cms.profile') }}">個人信息</a></li>
<!-- 密碼修改的url_for 重定向到/cms/resetpwd/ 路由在views.py中定義了 -->
<li><a href="{{ url_for('cms.resetpwd') }}">修改密碼</a></li>
<!-- 重定向到修改郵箱的url_for=/cms/resetemail/ -->
<li><a href="{{ url_for('cms.resetemail') }}">修改郵箱</a></li>
</ul>
</li>
<!-- 將全局變量的對象命名爲user -->
{% set user = g.cms_user %}
<!-- {{ url_for('cms.banners') }}綁定路由 -->
<li class="nav-group banner-manage"><a href="{{ url_for('cms.banners') }}">輪播圖管理</a></li>
<!-- 判斷是否有權限進行管理後臺,CMSPersmission.ALL_PERMISSION並沒有傳輸過來,無法識別,需要用到鉤子函數中的上下文管理器,在hooks.py中編寫 -->
{% if user.has_permissions(CMSPersmission.POSTER) %}
<li class="nav-group post-manage"><a href="#">帖子管理</a></li>
{% endif %}
{% if user.has_permissions(CMSPersmission.COMMENTER) %}
<li class="comments-manage"><a href="#">評論管理</a></li>
{% endif %}
{% if user.has_permissions(CMSPersmission.BOARDER) %}
<!-- {{ url_for('cms.boards') }}關聯路由 -->
<li class="board-manage"><a href="{{ url_for('cms.boards') }}">板塊管理</a></li>
{% endif %}
{% if user.has_permissions(CMSPersmission.FRONTUSER) %}
<li class="nav-group user-manage"><a href="#">前臺用戶管理</a></li>
{% endif %}
{% if user.has_permissions(CMSPersmission.CMSUSER) %}
<li class="nav-group cmsuser-manage"><a href="#">CMS用戶管理</a></li>
{% endif %}
{% if user.is_developer %}
<li class="cmsrole-manage"><a href="#">CMS組管理</a></li>
{% endif %}
</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>
渲染到後臺界面html文件:templates/cms/cms_boards.html
<!-- 繼承模板文件cms/cms_base.html 簡化代碼 -->
{% extends 'cms/cms_base.html' %}
<!-- 頁面標題 -->
{% block title %}
板塊管理
{% endblock %}
<!-- 標題 -->
{% block page_title %}
{{self.title()}}
{% endblock %}
{% block head %}
<script src="{{ url_for('static', filename='cms/js/boards.js') }}"></script>
{% endblock %}
{% block content %}
<div class="top-box">
<button class="btn btn-warning" style="float:right;" id="add-board-btn">添加新板塊</button>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>板塊名稱</th>
<th>帖子數量</th>
<th>創建時間</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 循環board信息, boards數據由視圖文件:apps/cms/views.py文件傳輸而來 -->
{% for board in boards %}
<tr data-name="{{ board.name }}" data-id="{{ board.id }}">
<td>{{ board.name }}</td>
<td>0</td>
<td>{{ board.create_time }}</td>
<td>
<button class="btn btn-default btn-xs edit-board-btn">編輯</button>
<button class="btn btn-danger btn-xs delete-board-btn">刪除</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
實現效果如下:
4、富文本編輯器
推薦使用:Editor.md
還可以百度一下其他的富文本編輯器:
進入官網後,下載安裝: