<>新经资讯是一个包含新闻首页,新闻详情,用户中心和后台管理等模块的Flask项目

*
项目框架的搭建

* 包含配置信息:mysql,redis,session等
* 工厂函数产出多种配置的app,数据库对象db,redis_store等
* 日志的记录
* 第三方库:云通讯,七牛云
* 设置的常量,自定义状态码,commons,models等
* flask_script,flask_migrate等
*
根据需求分析E-R图,构建模型类

![](D:\Users\Liu xiangyu\Desktop\新经资讯E-R图.png)

*
新闻首页和详情页模块

3.1 登录/注册/登出/状态保持(略),以下代码生成图片验证码,在form表单打开时调用
# 后端返回图片验证码使用响应体对象 resp = make_response(image) # 设置内容类型 resp.headers[
'Content-Type'] = 'image/jpg' return resp // 生成一个图片验证码的编号,并设置页面中图片验证码img标签的src属性
function generateImageCode() { // 1. 生成一个编号 // 严格一点的使用uuid保证编号唯一,
不是很严谨的情况下,也可以使用时间戳 imageCodeId = generateUUID(); // 2. 拼接验证码地址 var imageCodeUrl
= "/passport/image_code?code_id=" + imageCodeId; // 3. 设置页面中图片验证码img标签的src属性 $(
".get_pic_code").attr("src", imageCodeUrl)} // 一个最基础的ajax请求,负责给后端发送数据 var params
= { "mobile": mobile, "smscode": smscode, "password": password,} $.ajax({ url:
"/passport/register", type: "post", headers: { "X-CSRFToken": getCookie(
"csrf_token")}, data: JSON.stringify(params), contentType: "application/json",
success: function (resp) { if (resp.errno == "0"){ // 刷新当前界面 location.reload() }
else { $("#register-password-err").html(resp.errmsg) $("#register-password-err")
.show() } } } )
3.2 新闻点击排行

*
查询新闻表按照点击次数排序,转化成字典列表后返回数据并渲染模板
news_list = News.query. order_by(News.clicks.desc()).limit(constants.
CLICK_RANK_MAX_NEWS)
模板展示数据
<ul class="rank_list"> {% for news in data.click_news_list %} <li><span class="
{{ loop.index0 | indexClass }}">{{ loop.index }}</span><a href="#">{{
news.title }}</a></li> {% endfor %} </ul>
3.3 新闻分类展示(同租房城区列表,略)
categories = Category.query.all()
3.4 新闻列表实现

*
获取第几页page,每页数量per_page,分类category_id参数

*
检验参数后,查询数据并分页,后端有配合的js代码,详情见下文
filters = [] # 如果分类id不为1,那么添加分类id的过滤 if category_id != "1": filters.append(News
.category_id == category_id) paginate = News.query.filter(*filters). order_by(
News.create_time.desc()).paginate(page, per_page, False)
*
返回json数据,包括 total_pages,current_page,和新闻数据

3.5 定义一个装饰器判断用户是否登录
def user_login_data(f): #装饰器会改变被装饰函数的端点名称,采用此手段去除影响 @functools.wraps(f) def
wrapper(*args, **kwargs): # 获取到当前登录用户的id user_id = session.get("user_id") #
通过id获取用户信息 user = None if user_id: from info.models import User user = User.
query.get(user_id) g.user = user return f(*args, **kwargs) return wrapper
3.6 详情页新闻展示

* 获取news_id,查询news对象
* 和 user_info数据,新闻点击列表数据一同传给详情页模板进行渲染
3.7
收藏和取消收藏(前后端不分离的开发方式耦合度高,在这里体现的很明显,新闻详情页这个要渲染的模板中涉及的数据就很多,包括:is_collected,user_info,news,click_news_list,is_followed,comments等,要挤在一个视图函数中)

*
判断用户登录,获取 news_id,action

*
检验参数,获取新闻对象

*
根据action选择:append(news),还是remove(news),返回的是json数据。
if action == "collect": user.collection_news.append(news) else: user.
collection_news.remove(news)
3.8 新闻评论

* 判断用户是否登录,获取参数 news_id,comment_str,parent_id
* 检验参数,重点是判断新闻是否存在
* 创建评论模型类,保存数据提交,并且通过json返回评论数据(新闻详情页模板渲染时需要传评论数据,见下文)。
3.9 评论列表

*
获取news_id,取出新闻所有评论对象
comments = Comment.query.filter(Comment.news_id == news_id). order_by(Comment.
create_time.desc()).all() comment_list=[item.to_dict() for item in comments]
*
放入传给模板文件的data中,模板内判断是否有父评论
{% if comment.parent %} <div class="reply_text_con fl"> <div class="user_name2"
> {{ comment.parent.user.nick_name }}</div> <div class="reply_text"> {{
comment.parent.content }}</div> </div> {% endif %}
3.9 点赞

*
前端获取 comment_id,news_id,action参数,参数检验和用户登录检验

*
根据comment_id取出评论对象,根据 action 决定增加 comment_like 对象还是 remove(comment_like)对象,并对
comment.like_count 进行相应加减。以下是前端对应代码:
success: function (resp) { if (resp.errno == "0") { // 更新点赞按钮图标 if (action ==
"add") { // 代表是点赞 $this.addClass('has_comment_up') }else { $this.removeClass(
'has_comment_up') } }else if (resp.errno == "4101"){ $('.login_form_con').show()
; }else { alert(resp.errmsg) } }
*
取出该新闻下所有 评论对象,继而通过 评论id 取出 所有点赞对象,继而生成 被用户点赞的评论的id 组成的列表
comments = Comment.query. filter(Comment.news_id == news_id). order_by(Comment.
create_time.desc()).all() comment_ids = [comment.id for comment in comments]
comment_likes= CommentLike.query.filter (CommentLike.comment_id.in_(comment_ids)
, CommentLike.user_id == g.user.id).all() comment_like_ids = [comment_like.
comment_idfor comment_like in comment_likes]
*
先假定用户没有点赞评论,再判断该评论id是否在comment_like_ids,如果在如下操作,和用户收藏是相似的套路,而且都需要传给模板渲染。
comment_dict["is_like"] = True
*
个人中心

4.1 修改用户信息

*
后端更新并保存数据,更改状态保持即可

*
前端成功回调函数,多处name都要改变
if (resp.errno == "0") { // 更新父窗口内容 $('.user_center_name', parent.document).
html(params['nick_name']) $('#nick_name', parent.document).html(params[
'nick_name']) $('.input_sub').blur() }else { alert(resp.errmsg) }
4.2 用户收藏数据加载

*
先获取页数参数,设定默认值
collections = [] current_page = 1 total_page = 1
*
进行分页数据查询,给默认数据赋值,处理收藏数据列表后返回数据。前端展示分页界面的代码如下,内部的 currentPage,totalPage 是全局变量。
paginate = user.collection_news.paginate(p,constants.USER_COLLECTION_MAX_NEWS,
False) collections = paginate.items current_page = paginate.page total_page =
paginate.pages $(function() { $("#pagination").pagination({ currentPage: {{ data
.current_page }}, totalPage: {{ data.total_page }}, callback: function(current)
{ window.location.href = "/user/collection?p=" + current } }); });
4.2 用户发布新闻

*
获取新闻分类的数据,但要pop掉 ‘最新’
categories = Category.query.all() categories_dicts = [categorie.to_dict() for
categoriein categories] categories_dicts.pop(0)
*
获取前端提交的数据:title,digest,content,index_image,category_id,参数检验

*
读取图片上传七牛云,初始化新闻对象存储数据,设置 news.status=1

4.3 用户新闻列表(略)

*
其他

5.1 关注和取消关注

*
获取 user_id,action 参数,获取发布新闻的用户对象

*
根据action采取执行不同语句,返回json数据
if action == "follow": if target_user.followers.filter(User.id == g.user.id).
count() > 0: return jsonify(errno=RET.DATAEXIST, errmsg="当前已关注") target_user.
followers.append(g.user) else: if target_user.followers.filter(User.id == g.user
.id).count() > 0: target_user.followers.remove(g.user) // 关注当前新闻作者 $(".focus").
click(function () { var user_id = $(this).attr('data-userid') var params = {
"action": "follow", "user_id": user_id } $.ajax({ url: "/news/followed_user",
type: "post", contentType: "application/json", headers: { "X-CSRFToken":
getCookie("csrf_token") }, data: JSON.stringify(params), success: function (resp
) { if (resp.errno == "0") { // 关注成功 var count = parseInt($(".follows b").html()
); count++; $(".follows b").html(count + "") $(".focus").hide() $(".focused").
show() }else if (resp.errno == "4101"){ // 未登录,弹出登录框 $('.login_form_con').show()
; }else { // 关注失败 alert(resp.errmsg) } } }) })
5.2 其他用户界面

*
获取其他用户id,检验参数后,取出该用户对象

*
判断是否关注
is_followed = False if g.user: if other.followers.filter(User.id == user.id).
count() > 0: is_followed = True
*
其他用户新闻列表(略)

*
后台

6.1 管理员登录

*
GET请求获取模板页面后,前端提交两个参数 username,password

*
通过username取出用户对象,做密码验证
if not user.check_passowrd(password): return render_template('admin/login.html'
, errmsg="密码错误")
*
同时验证用户是否是管理员
{% if errmsg %} <div class="error_tip" style="display: block">{{ errmsg }}</div
> {% endif %}
*
使用端点名重定向到后台主页
return redirect(url_for('admin.admin_index'))
6.2 用户统计

*
获取到本月第1天0点0分0秒的时间对象,然后查询最后一次登录比其大的所有数据
now = time.localtime() mon_begin = '%d-%02d-01'%(now.tm_year, now.tm_mon)
mon_begin_begin= datetime.strptime(mon_begin, '%Y-%m-%d) mon_count =User.query.
filter(User.is_admin == False, User.create_time >= mon_begin_date).count()
*
获取到当日0点0分0秒时间对象,然后查询最后一次登录比其大的所有数据
day_begin = '%d-%02d-%02d' % (now.tm_year, now.tm_mon, now.tm_mday)
day_bedin_date= datetime.strptime(day_begin, '%Y-%m-%d) day_count = User.query.
filter(User.is_admin == False, User.create_time >= day_begin_date).count()
*
图表查询:遍历查询数据每一天的数据(当前天数,减去某些天)
now_date = datetime.strptime(datetime.now().strftime('%Y-%m-%d'), '%Y-%m-%d')
active_date= [] active_count = [] for i in range(0, 31): begin_date = now_date-
timedelta(days=i) end_date = now_date-timedelta(days=(i-1)) active_date.append(
begin_date.strftime('%Y-%m-%d')) count = 0 count = User.query.filter(User.
is_admin== False, User.last_login >= day_begin, User.last_login < day_end).count
() active_count.append(count) active_date.reverse() active_count.reverse()
6.3 用户列表(略)

6.4 新闻审核列表

*
新闻列表的关键字搜索的实现,后端接受了参数 page,keywords,增加过滤条件
filters = [News.status != 0] if keywords: # 添加关键词的检索选项 filters.append(News.
title.contains(keywords))
*
查询出新闻分页对象,返回前端即可

6.5 新闻审核详情

*
根据参数action的值修改新闻对象的status值,如果status=-1,需要增加reason
// 获取到所有的参数,尤其是reason $(this).serializeArray().map(function (x) { params[x.name
] = x.value; }); if (resp.errno == "0") { // 返回上一页,刷新数据 location.href = document
.referrer; }
6.6 用户新闻编辑

*
编辑详情界面数据,GET请求返回分类数据,新闻对象数据,要判断当前新闻属于哪个分类
categories = Category.query.all() categories_li = [] for category in categories
: c_dict = category.to_dict() c_dict["is_selected"] = False if category.id ==
news.category_id: c_dict["is_selected"] = True categories_li.append(c_dict) #
移除最新分类 categories_li.pop(0)
*

前端编辑提交,后端接受参数:news_id,title,digest,content,index_image,category_id,验证后,给news对象添加数据,保存,返回json数据。前端发送数据前需要的处理如下:
beforeSubmit: function (request) { // 在提交之前,对参数进行处理 for(var i=0; i<request.
length; i++) { var item = request[i] if (item["name"] == "content") { item[
"value"] = tinyMCE.activeEditor.getContent() } } }
6.7 新闻分类管理(略)

*
注意点:

*
当前端需要一次性展示页面效果,所需的数据不会动态变化使用模板渲染数据

*
当前端需要动态展示页面数据和处理事件,所需数据 根据情况变化时,使用ajax或者其他方式向后端发送异步请求。

*
先行假定数据一个安全值,然后推翻,赋予数据真实值,做到安全性的套路。
is_followed = False if g.user: if other.followers.filter(User.id == user.id).
count() > 0: is_followed = True

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信