Flask新经咨讯网站-----项目实战总结

Git远程仓库 https://gitee.com/cz_zzz/InfoNews18.git
1. 数据导入
2. 用户注册
3. 用户登录
4. 新闻首页
5. 新闻详情
6. 收藏新闻
7. 评论/回复
8. 点赞
9. 个人中心
10. 管理后台
11. 数据统计
12. 新闻后台管理

一:数据导入

1.导入测试数据 source /Users/zhangzz/Desktop/flask7/物料/测试数据/information_info_news.sql
2.注意先导入category(一) 在导入news(多 news中包含记录category主键的外键)

二. 路由基本实现

  • 根路由实现

    • 将模板文件拖入模板文件夹, 进行模板渲染
  • favicon设置

     - 网站的小图标默认会通过 /favicon.ico的url来获取
     - send_static_file可以返回静态文件
    

三. 用户注册

Web应用的基本开发流程

- 产品经理牵头开产品研讨会(UI, 前端开发, 后端开发, 项目经理), 展示产品说明文档,产品交互文档 	 
-  根据界面分析功能根据界面分析功能(前端,后端,项目经理) 	
-   定义表关系(后端) 	
-   设计接口文档 (和前端沟通协商, 一般由后端人员主导并编写)
- 同步开发(前端实现页面, 后端实现路由接口) 	
-  使用POSTMAN来测试接口 	
- 如果采用的是后端渲染, 前端完成页面后,
- 会将页面交给后端(静态页面也用GIT管理起来), 后端进行渲染并测试页面 	
- 如果发现错误, 需要和前端联调

获取图片验证码 (重点)
接口设计
获取和校验参数
生成图片验证码 需要安装pillow第三方包
将图片key和验证码文字一起保存
返回图片 设置自定义的content-type

- 获取短信验证码 (重点)

获取和校验参数
根据图片key取出验证码文字
校验验证码文字 过期/是否一致
发送短信
保存短信验证码
返回发送结果

  • 第三方短信集成
    应用管理- 新建应用
    模板使用ID=1
    添加测试号码
    使用3个id信息
  • 注册实现 (重点)
    获取&校验参数
    根据手机号取出短信验证码
    校验短信验证码
    保存数据
    json返回结果
  • 注册细节
    使用计算型属性封装密码的加密过程
    使用last_login属性来记录最后登录时间
    判断用户是否存在 发送短信前

一. 用户登录

  • 登录基本实现
    1.获取和校验参数
    2.根据手机号取出用户模型
    3.校验用户是否存在
    4.校验密码是否一致
    5.记录最后登录时间 SQLALCHEMY_COMMIT_ON_TEARDOWN = True # 设置每次请求 完成后自动提交 注意修改源代码
    6.状态保持
    7.将结果以json返回
  • 登录信息显示
    分析:
    1.在根路由中, 判断用户是否登录 session中是否保存了user_id
    2.如果登录, 根据user_id获取用户信息
    3.将用户信息传入模板, 进行模板渲染
  • 退出登录
    本质: 删除session中的user_id
    实现路由, 删除session中的user_id
    将结果以json返回

二. 新闻首页

  • 排行列表 (重点)

    分析:
    1.排行列表数据采用的是全局刷新, 前端/后端渲染都可以, 但是考虑SEO, 选择用后端渲染该数据
    实现:

     	1.在根路由中, 查询点击量排行前10的新闻
     	2.将数据传入模板, 进行模板渲染
     	3.自定义过滤器, 对索引的样式进行判断处理 过滤器的本质就是函数
    
  • 获取新闻列表 (重点)
    分析:

      - 1.首页新闻列表 采用的都是 局部刷新, 所以需要使用前端渲染, 后端只需要查询出数据, 并json返回
      - 2.不需要每个分类定义一个路由, 只需要前端传递分类id, 后端相应设置过滤条件即可
      - 3.分页展示新闻, 只需要前端传递当前页码cur_page和每页条数per_count即可
    

实现:

   1.获取&校验参数 格式转换
   2.根据参数查询目标新闻 查询指定分类&指定页码的新闻, 并按生成日期倒序排列
   3.将新闻包装为json并返回
   3. 获取分类列表

分析:

 1. 分类数据应该从数据库中查询出来, 再模板渲染 前后端渲染都可以, 这里采用后端渲染来完成 实现:
    
 2. 在根路由中, 查询所有的分类信息 将分类信息传入模板, 再进行模板渲染

三. 新闻详情

1. 显示详情页面 (重点)
分析:

  - 前端提供新闻id, 后端根据新闻id查询新闻所有的数据
  - 新闻内容需要SEO, 详情页采用后端渲染
  - 采用动态URL来获取新闻id, 查询字符串也可以完成该数据的传递

实现:

 - 1.使用路由变量获取到新闻id
 - 2.查询该新闻数据
 - 3.将数据传入模板, 进行渲染

一. 新闻详情

  • 显示其他数据
  • 抽取首页文件显示排行数据
  • 将排行前10的新闻数据传入详情页模板
  • 更新排行的链接
  • 设置点击量+1

二. 收藏新闻

  1. 收藏/取消收藏 (重点)

分析:

 - 局部刷新, 前端渲染, 返回json 
 - 本质: 让用户user和新闻news产生关联(用关系属性来建立/删除关系)

实现:

 - 获取&校验参数 
 - 根据action执行收藏/取消收藏(user和news建立/删除关系) ojson返回结果 
 - 设置模板中收藏的新闻id
 - 显示收藏情况 (重点)

分析:

 1. 在详情页路由中, 查询当前用户是否收藏了该新闻, 然后模板渲染 
 2. 收藏的显示是全局刷新, 前/后端渲染都可以, 这里采用后端渲染 收藏的显示是全局刷新, 前/后端渲染都可以, 这里采用后端渲染 

实现:

 1. 从详情页中, 查询当前用户是否收藏了该新闻 
 2. 将收藏情况传入模板, 进行模板渲染将收藏情况传入模板, 进行模板渲染

三. 评论/回复

  • 显示评论框
    根据用户是否登录, 显示评论框
  • 评论/回复基本实现 (重点)

分析

 1. 局部刷新, 前端渲染, 后端只需要返回json 
 2. 本质: 生成一条评论数据, 需要设置评论人user_id, 评论的新闻news_id
 3. 如果是回复评论, 还需要设置父评论id ojson返回时需要返回 评论内容(敏感词过滤)和评论id(为了回复时设置父评论id)如果是回复评论, 还需要设置父评论id 
 4. json返回时需要返回 评论内容(敏感词过滤)和评论id(为了回复时设置父评论id)json返回时需要返回 评论内容(敏感词过滤)和评论id(为了回复时设置父评论id)

代码

 1.获取&校验参数 
 2. 生成评论对象, 添加到数据库中 
 3.json返回数据, 需要返回评论内容和评论id
  • 显示评论情况 (重点)

    分析:
    在显示详情页面时, 查询该新闻的所有评论, 再进行模板渲染

    实现:

     在详情路由中, 查询该新闻的所有评论
     在模板中进行渲染
    

四. 点赞

  • 点赞/取消点赞

分析:

 - 和收藏类似, 点赞也是局部刷新, 后端返回json 
 - 点赞的本质: 用户user和评论comment建立/删除关系 
 - 点赞数量+1

实现:

 - 获取&校验参数 
 - 根据action执行点赞/取消点赞(user和commen建立/删除关系) 
 -  json返回结果

设置点赞数量的变化

  • 显示点赞情况

  • 分析:

    • 后端模板渲染
    • 显示”赞”还是显示”数量”
    • 点赞小手的颜色 判断哪些评论被”我”点过赞

一. 个人中心

  • 抽取基类

    • 1.将个人中心页面进行后端模板渲染
      2.个人中心只有登录才能访问, 需要加判断
      3.对个人中心页面进行模板抽取
  • 显示/修改基本资料 (重点)
    1.显示基本资料

  • 1.前端使用了frame(内嵌页面)的技术, 大页面中套小页面, 需要对小页面也进行模板渲染
    2.注意修改user.html中对小页面的访问链接

    • 1.使用同一个路由实现显示/修改个人资料
      2.实现逻辑:

      • 1.获取参数
        2.校验参数
        3.修改用户信息

4.返回json

  • 显示/修改头像
  • 1.使用同一个路由实现显示/修改个人资料
    2.实现逻辑:
    • 1.获取参数
      2.校验参数
      3.上传头像
      4.修改用户头像URL
      5.返回json, 将用户信息返回给前端
      • 封装七牛云工具
      • 修改密码
      • 显示收藏

1.显示收藏采用的是后端渲染, 后端查询出该用户收藏的新闻, 传入模板即可

  • 显示发布页面
  • 新闻发布
    1.同一个接口实现显示/提交(GET/POST)
    1.显示首页新闻时, 应该只显示审核通过的新闻(News.status == 0)
  • 显示发布列表
    1.和显示我的收藏类似, 只是需要修改查询条件和字典转换
  • 404 (重点)
    1.使用app.errorhandler来监听404错误
    2.抽取404.html, 并模板渲染
    3.将user信息传到模板中
    一. 管理后台
  • 后台登录
    1.login.html实现GET/POST
    1.实现后台的状态保持机制
  • 后台访问控制 (重点)
    1.对后台访问进行控制, 判断管理员是否登录
    2.使用蓝图的请求勾子进行拦截
    二. 数据统计
  • 添加测试数据
  • 显示注册数量 (重点)
    1.比较日期对象 获取当前日期->构建日期字符串->转换为日期对象
  • 统计注册曲线 (重点)
    1.每日注册人数: 注册日期 >= 当日0点, < 次日0点
    2.列表直接在模板中显示时, 注意字符串转义 添加safe过滤器取消转义
  • 用户列表
    1.和个人中心的我的收藏类似, 分页展示数据
    三. 新闻后台管理
  • 待审核列表
    1.和用户列表的实现机制类似
    2.传递keyword参数, 用于实现搜索筛选
  • 新闻审核详情
    1.待审核详情页面, 需要前端传递news_id, 使用动态URL的方式来处理
  • 新闻审核
    1.提交审核, 需要修改news模型, 使用POST的形式
    2.根据传递的news_id和action,修改模型的status
  • 新闻编辑列表
  • 新闻编辑详情
    1.和新闻审核类似, 通过动态URL传递了news_id
    2.需要查询出所有的分类数据, 并找出当前新闻对应的分类,标记后传递到模板中
  • 提交编辑
    1.POST方式提交编辑, 修改新闻的模型
  • 新增/修改新闻分类
    1.一个接口完成显示和修改分类(GET和POST)

一、项目分析
(一)新经资讯
1、新闻展示的Web项目
2、以抓取其他网站数据为新闻、用户发布数据为来源
3、基于Flask框架,前后端不分离
(二)技术实现
1、使用Flask框架实现
2、使用Redis + MySQL 进行数据存储
3、使用第三方扩展
(1)云通信
(2)七牛云
(三)功能模块分类
1、新闻模块
2、用户模块
3、后台管理模块

(四)项目目录说明
1、项目根目录 说明

 1. /info 项目应用核心目录 /logs 项目日志目录 
 2. config.py 项目配置文件--保存session信息、调试模式、密钥等
 3. config.py 项目配置文件--保存session信息、调试模式、密钥等config.py 项目配置文件--保存session信息、调试模式、密钥等
 4. manage.py 项目启动文件 requirements.txt     项目依赖文件manage.py 项目启动文件 requirements.txt     项目依赖文件

2、项目/info目录 说明

 - /libs 项目用到的资源库--第三方扩展(云通信) 		
 - /modules 项目模块--所有的蓝图对象和视图函数 		
 - /static 项目静态文件夹 		
 - /template 项目模板文件夹 		
 - /utils 项目通用设施--自定义状态码、上传图片等
 - __init__.py 项目应用初始化文件--应用程序实例、数据库实例、注册蓝图、日志等 
 - constants.py 项目常量信息--数据库缓存信息、验证码、房屋信息
 - models.py 项目模型类

3、项目/info/libs目录 说明

 - /yuntongxun 第三方扩展--发送短信 
 - sms.py 发送短信

4、项目/info/static目录 说明

 1. /admin/ 项目admin模块的静态文件,css/html/js等 		
 2. /news//news/项目admin模块的静态文件,css/html/js等 		
 3.  favicon.ico 项目logo favicon.ico 项目logo

5、项目/info/utils目录 说明

 1. image_storage.py 云存储设施文件--七牛云 
 2. response_code.py 自定义状态码response_code.py 自定义状态码

注意:
一、用工厂方法来实例化应用对象app
def create_app(config_name):
app = Flask(name) //实例化启动app
app.config.from_object(config[config_name]) //导入配置信息并动态传入配置信息
db.init_app(app) //关联db和app
Session(app) //把Session对象和app关联
CSRFProtect(app) // csrf保护关联app
此处使用请求钩子,在每次请求之后将csrf_token的值传给前端页面
**此处用来注册蓝图对象
from pro_info.modules.news import news_blue
app.register_blueprint(news_blue)
……
注册模板使用的过滤器
from pro_info.utils.common import do_index_class
app.add_template_filter(do_index_class, ‘index_class’)
return app

二、每次的请求都需要设置跨站保护,该方法可以使用请求钩子的方法,即每次请求之后执行,这个功能可以设置在实例化应用对象的时候就定义,即放在__init__.py文件中的实例化app对象的工厂方法中
@app.after_request
def after_request(response):
csrf_token = csrf.generate_csrf()
response.set_cookie(‘csrf_token’,csrf_token)
return response
并在前端的ajax中设置请求头
headers:{
X-CSRFToken:getCookie(‘csrf_token’)
}
三、由于很多接口需要判断用户是否在线的情况,对此我们采用的是用装饰器的方式实现这一功能的。
def login_required(f): è 定义一个方法,方便被调用
@functools.wraps(f) è 这是一个python内置的装饰器工具,目的是让被装饰的函数的属性不会被改变
def wrapper(*args,**kwargs):
user_id = session.get(‘user_id’) è 尝试获取用户的登录信息
user = None
if user_id:
try:
user = User.query.get(user_id)
except Exception as e:
current_app.logger.error(e)
g.user = user è 使用引用上下文的g变量来保存用户的信息
return f(*args,**kwargs)
//具体的实现方式是让被装饰的函数的名称在返回wrapper之前赋值给wrapper的__name__ è wrapper.name = f.name
return wrapper

四、统一设置返回的错误页面
由于用户的很多不恰当的操作,或者服务器的原因,导致页面无法显示等错误,我们可以设置指定的错误页面,可以使用
app.errorhandle(code_or_exception) 装饰器来实现这一功能,来达到与用户的更加友好的交互页面,写在__init__文件的工厂方法中

@app.errorhandle(错误码)
@user_login_data
def page_not_found(_):
user = g.user
data = {“user_info”:user.to_dict() if user else None}
return render_template(‘指定的错误页面的模板文件’,data=data)

五、为了更直观的展示后台数据效果,需要添加一些测试用户到数据库中,再目录下新建一个.py文件,复制如下代码直接运行即可

import datetime
import random
from info import db
from info.models import User
from manage import app
 
def add_test_users():
     users = []
     now = datetime.datetime.now()
     for num in range(0, 10000):
         try:
             user = User()
             user.nick_name = "%011d" % num
             user.mobile = "%011d" % num
         user.password_hash="pbkdf2:sha256:50000$SgZPAbEj$a253b9220b7a916e03bf27119d401c48ff4a1c81d7e00644e0aaf6f3a8c55829"
             user.last_login = now - datetime.timedelta(seconds=random.randint(0, 2678400))
             users.append(user)
              print(user.mobile)
         except Exception as e:
             print(e)
     # 手动开启一个app的上下文
     with app.app_context():
         db.session.add_all(users)
         db.session.commit()
     print('OK')
 
if __name__ == '__main__':
     add_test_users()

新闻首页模块
一、注册相关接口
(一)图片验证码
1、获取前端生成的UUID编码
image_code_id = generate()前端调用该方法生成UUID编号,并发送给服务器
由于这是一个imp标签所以服务器可以request.args.get()获取到这个编码
2、调用captcha(图灵测试)扩展生成图片验证码
对获取到的参数进行验证,判断是否存在
存在则:name,text,image = captcha.generate_captcha()调用captcha生成图片验证码
3、以前端获取的UUID为键,captcha生成的text为值,存储到Redis数据库中
使用Redis数据库 redis_store.setex(imageCodeId,time,text)将数据进行保存
4、使用flask中的make_response将图片返回给前端页面

response = make_response(image)

5、修改前端相应报头并返回

response. headers['Content-Type'] = 'image/jpg'
return….

(二)发送手机短信
1、根据接口文档指定请求方式,以及确定需要接受的参数
2、对接受的参数进行校验
(1)确认参数的完整性

if not all([mobile,image_code,image_code_id])
return…

(2)确认手机号是否符合规范(采用正则的方式验证)

if not re.match(r‘1[3456789]\d{9}’,mobile)
return…

(3)确认验证码是否正确,从Redis数据库中取出之前保存的text值进行比对,为防止二次使用验证码,取出后删除数据库中数据

real_image_code = redis_store.get(image_code_id)
redis_store. delete(…)
if not real_image_code.lower() != image_code.lower()
return…

3、参数校验完成后判断用户是否已经注册
查询MySQL数据库中是否存在该用户

user = User.query.filter_by(mobile=mobile).first()
if user 表示用户存在已经注册
return…

4、生成6位随机数,并以用户手机号位键,随机数位值将数据存储到Redis数据库

sms_code = '%06d' % random.randint(0,999999)
redis_store.setex(mobile,time,sms_code)

5、调用第三方云通讯接口发送生成的6位随机数给用户手机,
生成6位随机数:

sms_code = '%06d' % random.randint(0,999999)

调用云通讯发送短信:

ccp = sms.CCP()
         result = ccp.send_template_sms(mobile,[sms_code,time / 60],1)

如果result == 0 表示发送成功,else发送失败
(三)注册按钮
1、根据接口文档指定请求方式,以及确定需要接受的参数
2、对接受的参数进行校验
(1)确认参数的完整性

if not all([mobile,sms_code,password])
return…

(2)确认手机号是否符合规范(采用正则的方式验证)

if not re.match(r‘1[3456789]\d{9}’,mobile)
return…

(3)确认手机验证码是否正确,从Redis数据库中取出之前保存的sms_code值进行比对,为防止二次使用,取出后删除数据库中数据

real_sms_code = redis_store.get(mobile)

由于之前设置的是有时效的数据,因此需要判断是否还存在该sms_code
redis_store.delete(…)

if not real_sms_code != str(sms_code)
return…

3、将用户的数据存储到MySQL数据库

user = User()
user.moblie = mobile
user.password = password(加密存储)
user.nike_name = mobile(给用户默认设置一个昵称)
db.session.add(user)
db.session,commit()

4、为实现状态保持,将用户的注册的信息存储到Reids数据库中,并提示注册完成

     session['user_id'] = user.id
     session['mobile'] = user.mobile
     session['nick_name'] = mobile

二、登录相关接口
1、根据接口文档确定需要接受的参数以及请求方式
2、校验参数的完整性

if not all([mobile,password])

3、判断手机号码是否符合规则

if not re.match(r‘1[3456789]\d{9}’,mobile)
return…

4、根据手机号码进行MySQL的数据查找

user = User.query.filter_by(mobile=mobile).first()

5、判断是否存在用户或则密码是否正确

if user is None or not user.check_password(password)(调用加密密码的检查匹对方法)
return

6、保存用户的登录信息并记录用户的最后登录时间

    session['user_id'] = user.id
    session['mobile'] = mobile
    session['nick_name'] = user.nick_name

    user.last_login = datetime.now()

7、记录的用户登录时间需要提交到数据库中

db.session.commit()

8、返回提示用户登录成功
三、退出相关接口
退出即删除用户的登录信息

 session.pop('user_id')
 session.pop('mobile')
 session.pop('nick_name')

此处建议使用pop()方法,不建议使用clear()方法

四、主页相关接口
采用模板的方式,所以需要导入render_template
1、确认用户是否登录在线

  • user = g.user //此处是定义的装饰器

2、展示点击排行按点击量进行排序查找,且根据前端的设计是显示的六条信息
**

  • news_list = News.query.order_by(News.clicks.desc()).limit(6)

**
3、判断点击排行数据查找结果是否存在

if not news_list:
return…

4、定义一个列表容器保存查询结果

news_dict_list = []

5、遍历所有的查询对象并添加到列表容器中,并调用模型类中to.dict()的方法将之转换成字符串

for news in news_list:
news_dict_list.append(news.to_dict())

6、展示新闻分类数据需搜索所有分类信息

categories = Category.query.all()

7、判断分类数据是否存在

if not categories:
return

8、定义一个列表容器保存查询结果

category_list = []

9、遍历所有的查询对象并添加到列表容器中,并调用模型类中to.dict()的方法将之转换成字典

for category in categories:
category_list.append(category.to_dict())

10、定义一个字典保存所有数据,并将之传给模板进行数据填充

data = {
         "user":user.to_dict() if user else None,//表示如果用户未登录的情况下也是可以访问主页面的
          "news_dict_list":news_dict_list,
         "category_list":category_list
     }
return render_template(‘模板’,data=data)

五、首页新闻数据列表接口
1、根据接口文档定义路由,请求方式以及需要哪些参数等(备注:ajax/get请求)
2、接受所需要参数并进行校验(cid,page,per_page)
为实现友好的交互,即使后端没传过来数据,在首页也是需要展示新闻的,所以我们默认会给其定义一个参数

cid = request.args.get('cid','1') è分类id
     page = request.args.get('page','1')
     per_page = request.args.get('per_page','10')

3、校验参数,并将数字参数转换成整型值(为了和数据库进行查找)

try:
         cid,page,per_page = int(cid),int(page),int(per_page)
     except Exception as e:
return

4、根据参数进行查询数据库
(1)定义变量,存储过滤条件è根据分类id

filters = []
if cid > 1:  => filters.append(News.category_id == cid)

(2)默认按照分类id进行过滤,按新闻发布时间进行排序,对查询数据进行分页

paginate = News.query.filter(*filters).order_by(News.create_time.desc()).paginate(page,per_page,False)
paginate(页数,每页多少条目数,分页异常不报错)

5、获取分页后的新闻数据模型对象,总页数以及当前页数

     news_list = paginate.items //模型对象
     total_page = paginate.pages //总页数
     current_page = paginate.page //当前页数

6、遍历模型对象并添加到一个列表容器,使用to_dict()方法转换成字典

for news in news_list
news_dict_list.append(news.to_dict())

7、准备返回数据:

  • (1) data = {
  • (2) ‘news_dict_list’:news_dict_list,
  • (3) ‘total_page’:total_page,
    (4) ‘current_page’:current_page
    (5) }

8、进行返回 return jsonify(errno=RET.OK,errmsg=‘OK’,data=data)

新闻详情页模块
一、详情页模板接口
1、获取参数以url传参的形式获取参数

@蓝图对象.route(‘/<int:news_id>’)
def get_news_detail(news_id):

2、判断用户是否登录在线,并获取用户信息

user = g.user

3、根据news_id进行查询数据库

news = News.query.get(news_id)

4、检查查询的结果

if not news:
return…

5、展示详情页的分类数据,查询数据库

categories = Category.query.all()

6、检查查询的结果,并定义容器保存遍历的查询对象

if not categories:
return…

7、将新闻详情的点击次数 +1
news.clicks += 1
8、判断是否收藏,默认情况为False,如果用户已经登录,并且被该用户收藏

is_collected = False
   if user and news in user.collection_news:
       is_collected = True

9、评论内容,查询数据库获取当前新闻的所有评论

comments = []
comments = Comment.query.filter(Comment.news_id == news_id).order_by(Comment.create_time.desc()).all()

10、判断用户登录
if user:
获取所有的评论id

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()

获取当前用户点赞的评论id

  comment_like_ids = [comment_like.comment_id for comment_like in comment_likes]

11、定义一个列表用户来保存评论数据

comment_dict_li = []

12、遍历查询到所有评论数据

 for comment in comments:
 comment_dict = comment.to_dict()

判断评论是否被点赞,默认为False

    comment_dict['is_like'] = False
    if comment.id in comment_like_ids:
    comment_dict['is_like'] = True

将查询到的点赞的评论数据保存到之前定义的列表中

   comment_dict_li.append(comment_dict)

13、如果用户是登录的,查询用户关注的新闻发布者,默认为Falas

is_followed = False    
   if news.user and user:
       if news.user in user.followers:
           is_followed = True

14、将所有数据添加到一个字典容器中

data = {
       "news":news.to_dict(),
       "category_list":category_list,
       'user': user.to_dict() if user else None,//如果用户未登录,那么有关用户的信息就为False,新闻详情页没必要规定用户必须在线
       'is_collected': is_collected,
       'is_followed': is_followed,
       'comments': comment_dict_li
   }

15、调用模板页面返回数据,并对模板进行数据填充显示

   return render_template('news/detail.html',data=data)

二、收藏和取消收藏接口
根据接口文档进行路由分析,指定请求方式
1、尝试获取用户信息,如果用户未登录,需提示用户登录,才能进行收藏

user = g.user
if not user:
return…

2、获取参数news_id,action(collect,cancle_collect)

request.json.get()方法获取

3、检查参数,对news_id强转为整型值,对数据库进行查询,如果强转出错返回错误信息
4、检查action的值是否存在

if action not in ['collect', 'cancel_collect']:
return…

5、判断新闻是否存在数据库中

news = News.query.get(news_id)
if not news:
return…

6、判断选择的是collect还是cancle_collect,并判断用户是否收藏过该新闻,未收藏过添加到收藏,收藏过返回已收藏

if action == collect:
if news not in user.collection_news:
user.collection_news.append(news)
else:
If news in  user.collection_news:
User.collection_news.remove(news)

7、提交数据库
8、返回ajax响应信息

三、关注和取消关注接口
与收藏接口类似,只需根据接口文档定好路由以及请求方式,不过多赘述

四、新闻评论接口
根据接口文档确定路由及请求方式
1、获取用户登录信息,如果用户未登录直接返回并提示登录

user = g.user

2、获取参数,news_id,comment,parent_id(回复的评论的id)

request.json.get()方法

3、校验参数完整性

if not all([news_id,comment]):
return…

4、对news_id进行强转,并判断是否有parent_id,如果强转失败返回错误信息

news_id = int(news_id)
if parent_id:
parent_id = int(parent_id)

5、查询新闻并判断新闻是否存在

news = News.query.get(news_id)
if not news:
return…

6、实例化评论模型的对象,并添加数据

comment = Comment()
    comment.user_id = user.id
    comment.news_id = news.id
    comment.content = comment_conent

7、判断父评论是否存在,存在则添加父评论的信息

if parent_id:
comment.parent_id = parent_id

8、提交数据到数据库

try:
       db.session.add(comment)
       db.session.commit()
   except Exception as e:
       current_app.logger.error(e)
       db.session.rollback()

9、返回评论数据给前端ajax

五、点赞和取消点赞接口
根据接口文档确定路由及请求方式
1、获取用户登录信息,如果用户未登录直接返回并提示登录

user = g.user

2、获取参数,comment_id,action(add,remove)

request.json.get()方法

3、校验参数完整性

if not all([comment_id,action])
return…
if action not in [add,remove]
return…

4、把comment_id转成整型,强转错返回错误信息
5、根据comment_id查询数据

comment = Comment.query.get(comment_id)

6、判断查询结果,如果不存在,返回错误信息
7、判断action参数是否为add或者remove
(1)如果为add,判断行为是点赞还是取消点赞,使用CommentLike进行过滤查询(user_id,comment.id

comment_like_model = CommentLike.query.filter(CommentLike.user_id == user.id, CommentLike.comment_id == comment.id).first()
if not comment_like_model:è 点赞行为,点赞数加1
   comment_like_model = CommentLike()
       comment_like_model.user_id = user.id
       comment_like_model.comment_id = comment.id
       db.session.add(comment_like_model)
       comment.like_count += 1

(2)否则为取消点赞 è 取消点赞,点赞数减1

db.session.delete(comment_like_model)
       comment.like_count -= 1

8、将数据提交到数据库中

db.session.commit()

9、返回结果给ajax

个人中心模块
基于iframe进行实现,子页面的数据更新之后需要同步主页面相关联数据,可以采用js进行实现,本项目即采用了ajax的数据交互方式
一、用户页面接口
这里我们用到的是之前就定义好的装饰器来获取的用户信息
1、获取用户信息

user = g.user

2、判断是否获得了用户的信息,如果没有则重定向到主页

if not user:
return redirect(‘/’) 

3、将获取的用户信息使用字典容器保存起来
4、将数据返回给user.html模板,进行页面显示
二、用户基本信息展示和修改接口
根据接口文档确定路由以及请求方式[‘GET’,‘POST’]
1、判断请求方式,默认GET请求加载模板页面,可以直接以装饰器获取到用户的信息进行返回

user = g.user
if request.method == ‘GET’:
将用户信息返回给模板
return… è 模板为:user_base_info.html

2、获取参数(signature,nick_name,gender(男,女))

request.json.get()方法

3、校验参数的完整性

if not all([signature,nick_name,gender])
return…

4、检查参数gender参数

if gender not in [‘MAN’,‘WOMEN’]
return…

5、使用user对象将用户的基本信息保存到数据库

user.signature = signature
   user.nick_name = nick_name
    user.gender = gender
    try:
        db.session.add(user)
        db.session.commit()
    except Exception as e:
        current_app.logger.error(e)
        db.session.rollback()

6、返回结果

三、上传用户头像接口
根据接口文档确定路由以及请求方式[‘GET’,‘POST’]
1、判断请求方式,默认GET请求加载模板页面,可以直接以装饰器获取到用户的信息进行返回

user = g.user
if request.method == ‘GET’:
将用户信息返回给模板
return… è 模板为:user_pic_info.html

2、获取参数(avatar)

request.files.get()方法

3、校验参数是否存在

if not avatar:
return…

4、使用read()方法读取图片的二进制数据并保存

avatar_data = avatar.read()

5、调用第三方接口storage(),将图片上传到七牛云,并保存七牛云返回的图片名称

image_name = storage(avatar_data)

6、给用户保存图片的名称并提交到到数据库

user.avatar_url = image_name
try:
         db.session.add(user)
         db.session.commit()
     except Exception as e:
         current_app.logger.error(e)
         db.session.rollback()

7、拼接图片的绝对路径并返回给ajax
设置的七牛云地址 + image_name

四、修改用户密码接口
根据接口文档确定路由以及请求方式[‘GET’,‘POST’]
1、判断请求方式,默认GET请求加载模板页面

if request.method == ‘GET’:
return… è 模板为:user_pass_info.html

2、获取参数(old_password,new_password)

request.json.get()方法

3、检查参数完整性

if not all([old_password,new_password])
return…

4、获取用户信息,并比较用户输入的旧密码,验证是否正确

user = g.user
if not user.check_password(old_password):
return…

5、验证成功后保存用户的新密码,并提交到数据库

user.password = new_password
     try:
         db.session.add(user)
         db.session.commit()
     except Exception as e:
         current_app.logger.error(e)
         db.session.rollback()

6、返回结果给ajax

五、新闻发布接口
根据接口文档确定路由以及请求方式[‘GET’,‘POST’]
1、判断请求方式,默认GET请求加载模板页面

if request.method == 'GET':

查询新闻分类,与新闻首页模块的主页相关接口6、7、8、9雷同
return… è 模板为:user_ news_release.html
2、获取请求参数
request.form.get()方法获取表单参数
request.files.get()方法获取新闻图片参数

3、校验参数完整性

if not all([title,category_id,digest,index_image,content]):
         return…

4、强转分类id,如果报错直接返回错误信息
5、read()方法读取获取的新闻图片的二进制数据并调用第三方七牛云接口上传图片,并保存返回的图片名称

image_data = index_image.read()
        image_name = storage(image_data)

6、实例化新闻类对象,用来保存新闻数据

news = News() è 新闻类对象
news.title = title è 新闻标题
news.source = '个人发布' è 新闻发布机构
news.category_id = category_id è 新闻分类id
news.digest = digest è 新闻摘要
news.index_image_url = 设置的七牛云地址 + image_name è 新闻图片绝对路径
news.content = content è 新闻内容
news.status = 1 è 新闻状态,1表示待审核,0表示审核通过,-1表示未通过审核

7、提交数据到数据库中

六、用户新闻列表接口
根据接口文档确定路由和请求方式
1、获取参数,页数,默认1

request.args.get()方法

2、校验参数,将page强转为整型,如果报错直接返回错误信息
3、获取用户信息,定义容器存储查询结果,总页数默认1,当前页默认1
4、查询数据库,查询新闻数据并进行分页,

user = g.user
paginate = News.query.filter(News.user_id==user.id).paginate(page,每页数据数目 ,False)

可参看新闻首页模块的新闻列表接口
5、返回数据给模板:user_news_list.html

七、用户关注信息接口
根据接口文档确定路由和请求方式
与用户新闻列别接口流程类似
返回数据给模板:user_follow.html

八、查询用户关注的其他用户信息
根据接口文档确定路由和请求方式
1、获取用户的登录信息

user = g.user

2、获取参数other_id(用户关注的用户)

request.args.get()方法

3、校验参数是否存在,如果不存在,返回错误信息
4、查询信息

other = User.query.get(other_id)

5、判断新闻是否有作者,且用户关注过作者,默认为False

is_follwed = False
if other and user:
         if other in user.followed:
             is_follwed = True

6、返回数据给模板:other.html

九、返回指定用户发布的新闻接口
根据接口文档确定路由和请求方式
1、获取参数(user_id,页数默认为1)

request.args.get()方法获取

2、校验参数,强转页数的参数,如果报错,直接返回错误信息
3、判断用户是否存在

other = User.query.get(user_id)
if not other:
return…

4、如果用户存在,分页用户发布的新闻数据

paginate = other.news_list.paginate(page,每页条目数,False)

5、获取分页数据,并遍历数据

news_list = paginate.items
current_page = paginate.page
total_page = paginate.pages

6、返回数据给ajax

后台管理模块
主要是为了方便网站的管理而创建的后台管理员模块。管理员与普通用户公用一张表,管理员也具有普通用户的功能,用指定的字段区分普通用户和管理员用户,管理员可以登录到后台管理页面对新闻以及相应的数据进行操作。
一、创建管理员
使用flask-script扩展自定义脚本命令,以自定义函数的形式实现创建管理员用户

@manage.option('-n','-name',dest='name')
@manage.option('-p','-password',dest='password') //使用脚本扩展必须要的装饰器函数
def create_supperuser(name,password): //定义创建管理员的函数,并传入用户名和密码参数
     if not all([name,password]): //校验参数完整性
         print('参数缺失')
     user = User() //创建模型类User的对象
     user.nick_name = name // 添加相应的数据
     user.mobile = name
     user.password = password
     user.is_admin = True
     try: //提交数据到数据库
         db.session.add(user)
         db.session.commit()
     except Exception as e:
         db.session.rollback()
         print(e)
     print('管理员创建成功')
最后使用终端命令行创建管理员用户
python manage.py 函数名 -n 用户名 -p 密码

二、管理员登录请求钩子的应用 è 判断当前登录的用户是否是管理员,并且判断当前访问的url是否是管理员登录的页面url
因为后台管理不需要让每个普通用户都知道,所以在每次请求前先判断,在后台管理的模块中的创建蓝图的模块中就可使用before_request请求钩子实现该功能。具体方式如下:(在后台管理模块的 init.py 文件中)

@admin_blu.before_request //每次的请求之前执行的请求钩子
def check_admin():
     is_admin = session.get("is_admin", False) //从Redis数据库中获取用户登录状态的信息,默认为False
 //判断获取的is_admin,如果不是False表示是管理员,并判断访问的页面是否是管理员登录页面的url,否则返回主页,终止后续操作。
if not is_admin and not request.url.endswith(url_for('admin.login')):
        return redirect('/') //使用重定向返回到主页页面

三、后台管理首页接口
定义路由,返回后台管理首页模板页面

@admin_blu.route('/index') //定义路由
@login_required //确认用户是否登录
def index():
user = g.user //获取用户相关信息
    return render_template('admin/index.html',user=user.to_dict()) //返回后台管理首页模板

四、后台管理员登录接口
定义路由,指定请求方式(GET,POST)
1、如果为GET请求,使用session获取登录信息。

request.method == ‘GET’

2、判断用户是否登录并且是管理员,则重定向到后台管理页面

if user_id and is_admin:
return redirect(url_for(‘后台管理页面模板’)

3、否则返回后台管理登录页面模板

return render_template(‘后台管理登录页面’)

4、POST请求方式下获取参数(user_name,password)

request.form.get()方法

5、校验参数完整性

if not all([user_name,password]):
return…

6、查询数据库,并判断用户密码是否正确

user = User.query.filter(User.mobile==user_name,User.is_admin==True).first()
if user is None or not user.check_password(password):
return…

7、缓存用户信息,实现状态保持

session['user_id'] = user.id
     session['mobile'] = user.mobile
     session['nick_name'] = user.nick_name
     session['is_admin'] = user.is_admin

8、重定向到后来管理首页

return redirect(url_for('admin.index'))

五、后台用户数据展示接口
定义路由,获取数据一般默认为GET请求
1、根据页面显示内容包括总人数,月新增人数,日新增人数
2、查询数据库统计总人数,排除管理员用户的所有普通用户

总人数 = User.query.filter(User.is_admin == False).count()

3、查询数据库统计月新增人数,排除管理员用户的所有普通用户
(1)使用time模块获取时间对象(tm_year=2018, tm_mon=6, tm_mday=9)

t = time.localtime()

(2)通过时间对象生成每月开始日期的字符串

 mon_begin_date_str = '%d-%02d-01' %(t.tm_year,t.tm_mon)

(3)使用strptime()方法将日期的字符串转换成日期对象

 mon_begin_date = datetime.strptime(mon_begin_date_str,'%Y-%m-%d')

(4)作为过滤条件查询数据库,获取月新增人数

 mon_count = User.query.filter(User.is_admin == False,User.create_time > mon_begin_date).count()

4、查询数据库统计日新增人数,排除管理员用户的所有普通用户
具体步骤同统计月新增人数方式,先获取当前日期,生成字符串,再转换成日期对象,查询数据库加上过滤条件,获取日新增用户数据
5、实现统计活跃人数/活跃日期
定义列表容器存储活跃人数,和活跃日期
(1)获取当前日期,生成字符串,再转换成日期对象

today_begin_date_str = '%d-%02d-%02d' %(t.tm_year,t.tm_mon,t.tm_mday)
     today_begin_date = datetime.strptime(today_begin_date_str,'%Y-%m-%d')

(2)遍历日期,获取每天的开始日期和结束日期

for x in range(0,31): //一个月按30天

//开始时间,即每天的0时0分
begin_date = today_begin_date - timedelta(days=x) //timedelta()代表两个时间之间的时间差,两个data或datatime

//结束时间,即第二天的0时0分 //对象相减就可以返回一个timedelta对象,传入的参数表示前多少天
end_date = today_begin_date - timedelta(days=(x-1))

(3)使用时间的过滤条件查询数据库统计活跃日期的活跃人数

count = User.query.filter(User.is_admin == False, User.last_login >= begin_date, User.last_login < end_date).count()

(4)将需要的数据添加到之前定义的列表中

active_time.append(begin_date_str)
         active_count.append(count)

6、定义字典保存所需数据,将之返回给指定的模板进行渲染

data = {……}
return render_template('admin/user_count.html',data=data)

六、后台用户列表展示接口
1、获取参数,页数,默认为1

request.args.get()方法

2、校验参数,强转为int类型,如果错误,直接返回错误信息
3、查询数据库获取用户列表信息采用分页查找方式

paginate = User.query.filter(User.is_admin==False).paginate(page, 每页数目, False)
提取查询结果
current_page = paginate.page
         total_page = paginate.pages
         users = paginate.items

4、定义容器,遍历查询结果,并添加到容器中

for user in users:
         user_dict_list.append(user.to_admin_dict())

5、返回数据到指定的模板进行渲染

return render_template('admin/user_list.html', data=data)

七、后台新闻审核展示接口
1、获取参数,(页数,默认为1,关键字参数,默认为None)

request.args.get()方法

2、校验参数,强转页数为int类型,如果错误,直接返回错误信息
3、初始化变量,news_list[],current_page = 1,total_page = 1
4、设置过滤查询条件,当新闻状态不为0的情况下查询新闻数据

filters = [News.status != 0]

5、判断关键字参数是否存在,如果存则添加关键字搜索到过滤查询条件中

if keywords:
        filters.append(News.title.contains(keywords))

6、根据参数,进行分页过滤查询数据库并提取查询结果

paginate = News.query.filter(*filters).order_by(News.create_time.desc()).paginate(page, constants.ADMIN_NEWS_PAGE_MAX_COUNT, False)
提取查询结果
news_list = paginate.items
        current_page = paginate.page
        total_page = paginate.pages

7、遍历查询到的对象,组织数据返回给指定模板渲染

return render_template('admin/news_review.html', data=data)

八、后台新闻详情展示接口
通过url传入新闻id的参数,如下定义路由

@admin_blu.route('/news_review_detail/<int:news_id>')
def news_review_detail(news_id):
……

1、根据news_id查询新闻详情数据

news = News.query.get(news_id)

2、校验查询结果,如果未查到返回给指定模板错误信息

if not news:
         return render_template('admin/news_review_detail.html',data={'errmsg':'未查到数据'})

3、查询到结果调用to_dict()方法再传回指定模板渲染数据

return render_template('admin/news_review_detail.html',data= {"news":news.to_dict()})

九、后台新闻审核接口
根据需求,应该为POST请求,定义路由指定请求方式
1、获取参数(新闻id,action)

request.json.get()方法

2、校验参数完整性

if not all([news_id,action]):
return…

3、校验action参数是否为accept或reject

if action not in [accept,reject]:
return…

4、根据新闻id查询数据库

news = News.query.get(news_id)

5、判断是否查询到数据

if not news:
return…

6、判断action的具体值,accept表示接受,reject表示拒绝
如果为accept修改新闻状态为 0
如果为reject修改新闻状态为 -1
7、如果拒绝,接受拒绝原因
request.json.get()方法,获取拒绝内容
8、判断是否接受到接受原因,未接受到返回错误信息
9、将数据提交到数据库

try:
         db.session.commit()
     except Exception as e:
         current_app.logger.error(e)
        db.session.rollback()

10、返回信息

十、后台新闻版式编辑接口
1、获取参数(页数默认为1,关键字参数默认为None)
request.args.get()方法获取
2、校验参数,强转页数为int类型,如果错误,直接返回错误信息
3、初始化变量,news_list[],current_page = 1,total_page = 1
4、定义过滤条件,并判断关键字参数是否存在,如果存在,添加到过滤条件中

filters = [News.status != 0]
     if keywords:
         filters.append(News.title.contains(keywords))

5、根据相关数据进行分页查询数据库,并保存到之前初始化的遍历中

paginate = News.query.filter(*filters).paginate(page,每页数目,False)
        news_list = paginate.items
        current_page = paginate.page
        total_page = paginate.pages

6、遍历查询数据对象,调用to_basic_dict()方法

news_dict_list = [] //定义一个容器接受
     for news in news_list:
         news_dict_list.append(news.to_basic_dict())

7、组织好数据返回给指定的模板进行渲染

return render_template('admin/news_edit.html',data=data)

十一、后台新闻编辑详情接口
根据需求判断,应该是GET请求和POST请求,定义路由,和请求方式
1、判断是否是GET请求
2、获取参数新闻id,校验参数存在,强转int,如果错误,返回错误
3、根据新闻id获取新闻数据
4、校验查询数据是否存在,查询错误或则查询失败直接返回给指定模板错误信息
5、查询分类信息并移除最新分类,使用pop方法
6、遍历分类信息,并判断当前遍历到的分类和新闻所属分类是否一致
8、所有条件成立的情况下,组织数据返回给指定模板进行渲染
9、如果为POST请求,获取参数(news_id,title,digest,content,index_image,category_id)
request.form.get()获取表单中的数据
request.files.get()获取图片文件
10、校验参数完整性,与之前大同小异
11、根据新闻id查询数据库,确认新闻是否存在,与之前大同小异
12、读取图片数据,调用第三方接口(七牛云)上传图片并保存七牛云返回的图片名称,拼接图片的绝对路径
13、将数据保存到数据库进行提交
14、返回结果。(大部分操作可参照个人中心模块新闻发布接口

十二、后台新闻分类修改接口
根据需求判断请求方式应该为GET和POST,定义路由
1、判断如果是GET请求
2、查询所有分类数据,遍历查询结果,并移除最新分类(使用pop()方法)
3、组织数据返回给指定模板进行渲染
4、如果是POST请求
5、获取参数(name,cid) //如果有cid表示编辑已存在的分类
request.json.get()方法获取
6、校验参数name的存在
7、判断cid是否存在,如果存在即修改已有的分类,强转为int类型
8、根据分类cid查询数据库,校验查询结果
9、修改cid的分类信息为name的值
10、实例化分类模型类的对象,保存分类名称,并将数据库提交到数据库
11、返回结果

猜你喜欢

转载自blog.csdn.net/qq_43475097/article/details/83857030
今日推荐