管理信息系统 课程设计
1.系统概要说明
本论坛网站系统使用Pycharm开发工具利用flask框架进行开发。
使用AJAX技术进行页面前端数据传输,后台根据获取数据进行json判断,并在页面进行弹框提示。
在项目文件中建立py后台文件进行json返回不同参数。
提示框使用sweetalert封装好的架构。在前端js文件判断使用不同的提示框进行提示。
发布与评论文本框采用ueditor封装架构,可以将文本内容以html语言进行包装、可以进行换行、扩大字体、添加图片、表情包等等操作。。。
本系统:
用户注册、用户登录、用户个人中心、上传头像、用户修改密码、用户修改个人信息。
用户发布帖子、评论帖子、点赞、收藏。
板块分类查询、加精热门文章、搜索、文章分页一系列功能。
2.网站结构设计
网站前端采用DIV+CSS布局并少量添加bootstrap框架
总体简洁大方、以白色、橙色、与蓝色为主。
字体采用默认字体或微软雅黑、
父模板以导航条为模板、首页有导航条、板块分类、大量帖子进行列表布局、底部有分页效果:具体样式为:
登录和注册页面中使用div+css样式将登录和注册表单放置在页面中心,具体样式为:
个人中心在继承父模板的基础上进行个人首页的美化加工,再以个人中心为父模板进行,我的发布、我的收藏进行继承。实现列表布局。具体样式:
加精页面采取bootstrap表格进行列表循环、并用按钮进行加精操作。具体样式为:
3.模块详细设计
为了使Python文件结构更加清晰和易于优化,文件采用了模型分离的结构,将数据库模型和数据库使用语句代码,分别放置在models.py和exts.py 文件,再将数据库的管理使用又单独放置在manage.py的文件中。
数据库的创建不再是在系统运行之后自动建立而是需要在cmd命令窗口进行映射。
其三行语句分别为:
Python manage.py db init
Python manage.py db migrate
Python manage.py db upgrade
这样添加数据库表的好处是,在你想添加新的表或者在表中添加新的列属性时,不需要重新删除数据库重新建立,只需要再次在命令窗口输出后两行代码进行映射就可以的。这样添加进的数据就不用重新添加。
在本次系统编写中不再是以上次大作业将所有功能视图代码和所有的装饰器功能放在一个文件中而是放置在不同的目录下,进行分组后台视图操作,首先建立apps为所有后台功能文件,在apps里面添加front和comment文件分为前台用户视图和共用视图。同理在static前端样式和js也是同样操作
在front文件中分装将后台视图功能、装饰器功能、表单功能、判断功能分别放入不同的文件当中。
本次论坛网站是在上学期的基础上功能添加,所以此次论文将不再提及上学期的的功能。而重点提及此次新添加的功能
3.1修改密码功能
系统概述中以及提及本次所有数值传输都是利用Ajax技术进行页面传值。所以数值的传输的代码编写复杂了许多,而且容易犯错,打错一个单词就需要大量时间。
具体代码如下:
后台views视图:
#用户修改密码
@bp.route('/resetpwd/',methods=['GET','POST'])
@login_required
def resetpwd():
if request.method == 'GET':
return render_template('front/front_resetpwd.html')
else:
form = ResetpwdForm(request.form)
if form.validate():
oldpwd = form.oldpwd.data
newpwd = form.newpwd.data
user = g.front_user
if user.check_password(oldpwd):
user.password = newpwd
db.session.commit()
# {"code":200,message=""}
# return jsonify({"code":200,"message":""})
return restful.success()
else:
return restful.params_error("旧密码错误!")
else:
return restful.params_error(form.get_error())
3.2分类与分页功能
其中分类功能需要我们添加一个版块分类的模型和数据库,其中记录版块id版块的名称和时间,再在帖子表中加入其版块id的外键,在发布帖子中记录下版块的id。
在首页后台视图中再根据页面是否获取到版块id来进行if判断如获取到版块id返回对应版块id 的帖子
其中分页功能我们需要添加flask_paginate库,根据Pagination,get_page_parameter
的方法进行判断和分页效果。
再将分类和分页功能进行结合实现出可以根据展出版块类别的数量进行分页效果:
具体代码为:
后台views视图代码
@bp.route('/')
def index():
board_id = request.args.get('bd',type=int,default=None)
page =
request.args.get(get_page_parameter(),type=int, default=1)
boards = BoardModel.query.all()
start =(page-1)*config.PER_PAGE
end = start + config.PER_PAGE
posts=None
total = 0
query_obj=PostModel.query.order_by(PostModel.create_time.desc())
if board_id:
query_obj = query_obj.filter_by(board_id=board_id)
posts =
query_obj.slice(start,end)
total = query_obj.count()
else:
posts =
query_obj.slice(start,end)
total = query_obj.count()
pagination = Pagination(bs_version=3,page=page,total=total,outer_window=0,inner_window=2)
context={
'boards': boards,
'posts':posts,
'pagination':pagination,
'current_board':board_id
}
return render_template('front/front_index.html',**context)
前台HTML代码:
<div class="main1">
<!--<span style="margin-left: 20px;
background: #ccc; font-size:28px ; color: royalblue">热搜TOP10!!</span>-->
<ol>
{% for foo in posts %}
<li><a href="{{ url_for("front.post_detail",post_id=foo.id) }}">{{ foo.title
}}</a> {% if foo.highlight%}<span style="background: red;color: #fff;">加精贴</span>
{% else %}
<span style="background: #ccc">普通贴</span>
{% endif %}<br><span>{{ foo.create_time
}}</span> <span>作者:{{ foo.author.username }}</span> </li>
{% endfor %}
</ol>
{{ pagination.links }}
</div>
<div class="main3">
<ul>
{% if current_board %}
<a href="/" class="list-group-item
"><li>所有版块</li></a>
{% else %}
<a href="/" class="list-group-item
active"><li>所有版块</li></a>
{% endif %}
{% for board in boards %}
{% if board.id
== current_board %}
<a href="{{ url_for("front.index",bd=board.id) }}" class="list-group-item
active"><li>{{ board.name }}</li></a>
{% else %}
<a href="{{ url_for("front.index",bd=board.id) }}" class="list-group-item
"><li>{{ board.name }}</li></a>
{% endif %}
{% endfor %}
</ul>
</div>
3.3帖子加精与取消加精功能
其中加精功能需要我们添加一个加精的模型和数据库,其中进行帖子id的添加,只要记录在加精的数据库中这篇帖子就是加精贴,再根据加精贴模型的使用对后台视图函数进行排序就可以将加精的帖子做到置顶、放置热门的操作。基本的操作就是将帖子添加进加精数据中。
在前端html执行加精操作即可。
具体代码为:
后台views视图代码
@bp.route('/hignlight')
def hignlight():
post = PostModel.query.all()
return render_template('front/front_allpost.html',post=post)
@bp.route('/hpost',methods=['POST'])
def hpost():
post_id = request.form.get("post_id")
if not post_id:
return restful.params_error('请传入帖子id')
post=PostModel.query.get(post_id)
if not post:
return restful.params_error("没有这篇帖子!")
hignlight = HighlightPostModel()
hignlight.post = post
db.session.add(hignlight)
db.session.commit()
return restful.success()
@bp.route('/upost',methods=['POST'])
def upost():
post_id = request.form.get("post_id")
if not post_id:
return restful.params_error('请传入帖子id')
post=PostModel.query.get(post_id)
if not post:
return restful.params_error("没有这篇帖子!")
hignlight =
HighlightPostModel.query.filter_by(post_id=post_id).first()
db.session.delete(hignlight)
db.session.commit()
return restful.success()
前台html代码:
<div style="width: 1000px; margin: 50px auto; ">
<table class="table
table-bordered" style="text-align: center">
<thead>
<tr>
<th>帖子标题</th>
<th>作者</th>
<th>发布时间</th>
<th>所属板块</th>
<th>操作</th>
</tr>
</thead>
{% for foo in post %}
<tr data-id="{{ foo.id
}}" data-highlight="{{ 1 if
foo.highlight else 0}}">
<th><a target="_blank" href="{{ url_for("front.post_detail",post_id=foo.id) }}">{{ foo.title
}}</a></th>
<th>{{ foo.author.username }}</th>
<th>{{ foo.board.name }}</th>
<th>{{ foo.create_time
}}</th>
<th>
{% if foo.highlight
%}
<button type="button" class="btn btn-default
highlight-btn">取消加精</button>
{% else%}
<button type="button" class="btn btn-danger
highlight-btn">加精</button>
{% endif %}
</th>
</tr>
{% endfor %}
</table>
3.4点赞收藏功能
点赞收藏功能都是需要建立点赞表和收藏表,其原理大致相同,都是获取到用户的id与帖子的id再进行是否在数据库中有此数据进行判断是否可以进行点赞和收藏的操作。点击赞或者收藏就在数据库中添加数据达到效果。
具体代码为:
后台views视图代码:
#点赞功能
@bp.route('/dianzan/',methods=['GET','POST'])
@login_required
def dianzan():
user_id=g.front_user.id
post_id=request.form.get('post_id')
dianzan=DianzanModel(user_id=user_id,post_id=post_id)
db.session.add(dianzan)
db.session.commit()
return redirect(url_for('front.post_detail',post_id=post_id))
#收藏功能
@bp.route('/collection/',methods=['GET','POST'])
@login_required
def collection():
user_id=g.front_user.id
post_id=request.form.get('post_id')
collection=CollectionModel(user_id=user_id,post_id=post_id)
db.session.add(collection)
db.session.commit()
return redirect(url_for('front.post_detail',post_id=post_id))
前台html数据传输:
<form action="{{ url_for('front.collection') }}" method="post">
<input type="hidden" name="post_id" value="{{ post.id
}}">
{% if collection %}
<button type="button" class="btn btn-danger
highlight-btn">已收藏</button>
{% else %}
<button type="submit" class="btn btn-default
highlight-btn" id="collection_btn">收藏</button>
{% endif %}
</form>
<form action="{{ url_for('front.dianzan') }}" method="post">
<input type="hidden" name="post_id" value="{{ post.id
}}">
{# <input
type="hidden" name="post_id" value="{{ g.front_user
}}">#}
{% if dzyes %}
<button type="button" class="btn btn-default
btn-xs pull-right"><span
class="glyphicon glyphicon-heart">{{ post.dianzan
|length }}</span></button>
{% else %}
<button type="submit" class="btn btn-success
btn-xs pull-right"><span
class="glyphicon
glyphicon-heart-empty">赞</span></button>
{% endif %}
</form>
3.5上传头像功能
在原来用户表的基础上添加了一个存储头像路径的列,其中我们将页面上传的图片的具体图片的内容存储到后台给的路径中存储,而数据库的列属性则是存储路径的链接文本,并在页面中根据其具体路径显示头像,而在用户没有上传头像的时候则放置默认的头像。
具体代码为
后台views视图代码
#用户上传头像
@bp.route('/avatar/<user_id>',methods=['POST'])
@login_required
def updata_acatar(user_id):
user =
FrontUser.query.filter(FrontUser.id == user_id).first()
f = request.files['img']
basepath = os.path.dirname(__file__) #
当前文件所在路径
upload_path = os.path.join('E:/godlike/static/img', f.filename) #
注意:没有的文件夹一定要先创建,不然会提示没有该路径
f.save(upload_path)
user.avatar = 'img/' + f.filename
db.session.commit()
return redirect(url_for('front.usercenter',user_id=user.id,tag=1))
前台html传输
{% if user.avatar
is none %}
<img src="/static/img/icon_base.jpg"
width="120px" height="120px" alt=""/>
{% else %}
<img src="/static/{{ user.avatar
}}" alt=""
width="110px" height="110px">
{% endif %}
<form action="{{ url_for('front.updata_acatar',user_id=user.id) }}" method="post"
enctype="multipart/form-data">
<input type="file" name="img" required style="opacity: 0
; width: 80px;height: 30px; position: relative;top:30px;left: 5px;">
<label for="" class="ui_button
ui_button_primary" style="border: 1px solid #00a5e0; border-radius:5px; background-color: #00a5e0;color: #fff; width: 80px;height: 32px;text-align: center;padding: 5px">上传头像</label>
<button type="submit" class="btn btn-default
highlight-btn">确认上传</button>
</form>
4.数据库设计
数据库分为用户与总体帖子数据表
分为:
用户表
id:用户id(主键、自增长、默认值为shortuuid)
username:用户名(列属性、String类型、不可为空)
_password:用户密码(列属性、String类型、不可为空,设置哈希加密)
email:邮箱(列属性、String类型、不可重复、不可为空)
realname:真实姓名(列属性、String类型)
avatar:用户头像(列属性、String类型)
signature:个性签名(列属性、String类型)
join_time:加入时间(列属性、DateTime类型,默认值为添加时间)
帖子表
id:帖子id(主键、Int类型、自增长)
title:帖子标题(列属性、String类型、不可为空)
content:内容(列属性、Text类型、不可为空)
create_time:创建时间(列属性、DateTime类型、默认值为创建时间)
board_id:板块id(外键、Int类型、外键为border.id)
author_id:作者id(外键、String类型、外键为front_user.id)
评论表
id:帖子id(主键、Int类型、自增长)
content:内容(列属性、Text类型、不可为空)
create_time:创建时间(列属性、DataTime类型、默认值为创建时间)
post_id:帖子id(外键、Int类型、外键为post.id)
author_id:用户id(外键、String类型、外键为front_user.id)
板块表
id:板块id(主键、Int类型、自增长)
name:板块名称(列属性、String类型、不可为空)
create_time:创建时间(列属性、DataTime、默认值为创建时间)
点赞表
id:板块id(主键、Int类型、自增长)
post_id:帖子id(外键、Int类型、外键为post.id)
user_id:用户id(外键、String类型、外键为front_user.id)
收藏表
id:板块id(主键、Int类型、自增长)
user_id :用户id(外键、String类型、外键为front_user.id)
post_id:帖子id(外键、Int类型、外键为post.id)
create_time:创建时间(列属性、DateTime,默认值创建时间)
数据库的连接关系:
5.系统实现的关键算法与数据结构
关键算法是if根据前端传到的数据在后台进行比对,再根据传输的code的值进行判断,再根据js文件的判断,在页面上展示出不同的效果。
其中其code值是如何传输的?这就需要调用其中resful文件中,不同类的定义了。
具体代码为:
class HttpCode(object):
ok = 200
unautherror = 401
paramserror = 400
servererror = 500
def restful_result(code,message,data):
return jsonify({"code":code,"message":message,"data":data or {}})
def success(message="",data=None):
return restful_result(code=HttpCode.ok,message=message,data=data)
def unauth_error(message=""):
return restful_result(code=HttpCode.unautherror,message=message,data=None)
def params_error(message=""):
return restful_result(code=HttpCode.paramserror,message=message,data=None)
def server_error(message=""):
return restful_result(code=HttpCode.servererror,message=message or '服务器内部错误',data=None)
其中后台视图会根据表单验证判断结果返回出不同种类的方法回调,输出不同的code值。例如return restful.success(),就是执行操作成功返回成功,这时就返回code值为200代表成功。再在js文件中调用,根据其成功的传值返回不同的提示框。如message为返回的文本提示。
以注册功能的代码为例子:
后台view视图:
#注册功能后台视图
class SignupView(views.MethodView):
def get(self):
return_to = request.referrer
if return_to and return_to != request.url and safeutils.is_safe_url(return_to):
return render_template('front/front_signup.html',return_to=return_to)
else:
return render_template('front/front_signup.html')
def post(self):
form = SignupForm(request.form)
if form.validate():
email = form.email.data
username = form.username.data
password = form.password1.data
user = FrontUser(email=email,username=username,password=password)
db.session.add(user)
db.session.commit()
return restful.success()
else:
print(form.get_error())
return restful.params_error(message=form.get_error())
后台表单判断:
class SignupForm(BaseForm):
email = StringField(validators=[Email(message='请输入正确的邮箱格式'), InputRequired(message='请输入邮箱')])
username = StringField(validators=[Regexp(r".{2,20}", message='请输入正确格式的用户名!')])
password1 = StringField(validators=[Regexp(r"[0-9a-zA-Z_\.]{6,20}", message='请输入正确格式的密码!')])
password2 = StringField(validators=[EqualTo("password1", message='两次输入的密码不一致!')])
前端js文件传输判断:
$(function () {
$("#submit-btn").click(function (event) {
event.preventDefault();
var email_input = $("input[name='email']");
var username_input = $("input[name='username']");
var password1_input = $("input[name='password1']");
var password2_input = $("input[name='password2']");
var email = email_input.val();
var username = username_input.val();
var password1 = password1_input.val();
var password2 = password2_input.val();
zlajax.post({
'url': '/signup/',
'data': {
'email':email,
'username': username,
'password1': password1,
'password2': password2
},
'success': function(data){
if(data['code'] == 200){
var return_to = $("#return-to-span").text();
if(return_to){
window.location = return_to;
}else{
window.location = '/';
}
}else{
zlalert.alertInfo(data['message']);
}
},
'fail': function(){
zlalert.alertNetworkError();
}
});
})
})
前台多重条件判断,里面包括板块分类、分页功能的结合。不止根据分类获取帖子内容,不会出现换页而出现板块分类消失的情况
具体代码为:
@bp.route('/')
def index():
board_id = request.args.get('bd',type=int,default=None)
page = request.args.get(get_page_parameter(),type=int, default=1)
boards = BoardModel.query.all()
start =(page-1)*config.PER_PAGE
end = start + config.PER_PAGE
posts=None
total = 0
query_obj=PostModel.query.order_by(PostModel.create_time.desc())
if
board_id:
query_obj = query_obj.filter_by(board_id=board_id)
posts =
query_obj.slice(start,end)
total = query_obj.count()
else:
posts =
query_obj.slice(start,end)
total = query_obj.count()
pagination = Pagination(bs_version=3,page=page,total=total,outer_window=0,inner_window=2)
context={
'boards': boards,
'posts':posts,
'pagination':pagination,
'current_board':board_id
}
return render_template('front/front_index.html',**context)
在此次系统中我们放弃了上下文处理器的选择,而重新选择了一种新的函数方法,g函数,g函数是指每次用户登录都会记录和调用其本用户的的数据内容,不需要在后台重新定义一个视图函数在继承在页面中,减少了代码的冗余。提高了数值传递效率。非常的好用,前后台都可以进行使用,只需在hooks文件中添加:
@bp.before_request
def my_before_request():
if config.FRONT_USER_ID in session:
user_id =
session.get(config.FRONT_USER_ID)
user =
FrontUser.query.get(user_id)
if user:
g.front_user = user
并在views视图和前台html文件调用方法即可
6.成品展示
用户注册与登录
注册与登录限制
注册成功跳转过快截不到图就不放置图片了。
用户发布帖子
帖子详情页
发布评论
点赞与收藏
版块分类
分页功能
加精功能
个人中心
我的发布
我的收藏
上传头像
修改密码
7.个人总结
本次论坛网站系统的设计和开发中我学习到了许多flask开发的技巧,了解到了一个系统的开发是多么的不容易,每一个代码的编写都不可有一点错误,牵一发动全身,尤其是ajax传值进行添加修改操作更是即繁琐又需要耐心。
并在此感谢黄老师的教导,让我在短短的一个月中有了很大的进步。