根据权重查询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) # 图形文件,图形类
# 从当前页面跳转过来就是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">
<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">
{% for banner in banners %}
{% if loop.first %}
<div class="item active">
{% else %}
<div class="item">
{% endif %}
<img src="{{ banner.image_url }}" alt="..." style="width: 300px;height: 300px">
<div class="carousel-caption">
div>
div>
{% endfor %}
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">Previousspan>
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">Nextspan>
a>
div>
div>
div>
{% endblock %}
注意七牛云上传文件的时候,上传空间的地域需要选择地区为:华东
否则上传文件不成功,报错: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 %}
<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>
{% 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>
<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>
<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 发送
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);
}
});
});
创建板块管理中的文章模型类,再映射到数据库中:后台模型文件: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
<html lang="en">
<head>
<meta charset="UTF-8">
<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>
<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>
<link rel="stylesheet" href="{{ url_for('static', filename='common/sweetalert/sweetalert.css') }}">
<script src="{{ url_for('static', filename='common/sweetalert/lgalert.js') }}">script>
<script src="{{ url_for('static', filename='common/sweetalert/sweetalert.min.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 navigationspan>
<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 }}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="{{ url_for('cms.resetpwd') }}">修改密码a>li>
<li><a href="{{ url_for('cms.resetemail') }}">修改邮箱a>li>
ul>
li>
{% set user = g.cms_user %}
<li class="nav-group banner-manage"><a href="{{ url_for('cms.banners') }}">轮播图管理a>li>
{% 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) %}
<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
{% 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>
{% for board in boards %}
<tr data-name="{{ board.name }}" data-id="{{ board.id }}">
<td>{{ board.name }}td>
<td>0td>
<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 %}