15.新闻项目——新闻前台——个人中心(1)
关于新闻详情的内容告一段落,接下来,我们要完成个人中心的相关内容,个人中心应该是一个独立的模块,所以我们首先新建一个模块来专门存储关于个人中心的相关内容。
前期准备
1.建立文件夹
在modules中新建一个名为user的python package的文件夹,并在文件夹中新建views.py用于存储视图函数。
2.初始化蓝图
info -> modules -> user -> __init__.py
from flask import Blueprint user_blue = Blueprint('user', __name__, url_prefix='/user') from . import views
3.注册蓝图
info -> __init__.py
from info.modules.user import user_blue app.register_blueprint(user_blue)
4.获取静态文件
进入info -> static -> news -> html -> user.html拖动到info -> templates -> news中
5.加入视图函数
在views.py中加入视图函数,将user.html文件渲染出来
info -> modules -> user -> views.py
# 个人中心 from . import user_blue from flask import render_template @user_blue.route('/info') def user_info(): """个人中心入口""" return render_template('news/user.html')
右上角逻辑处理
个人中心和之前的首页和新闻详情页不同,这个页面只有登陆的用户才可以进来。而在页面右上角有我们熟悉的用户登陆信息的相关内容,所以这里我们做统一的修改。
后端逻辑:
# 个人中心 from info.utils.comment import user_login_data from . import user_blue from flask import render_template, redirect, url_for, g @user_blue.route('/info') @user_login_data def user_info(): """个人中心入口 提示:必须是登录用户才能进入 """ # 获取登录用户信息 user = g.user if not user: return redirect(url_for('index.index')) context = { 'user': user } return render_template('news/user.html', context=context)
说明:
- 如果没有蓝图,redirect(url_for('视图函数名字'))
- 如果有蓝图,redirect(url_for('蓝图名字.视图函数名字'))
后端逻辑处理完成之后,我们接着处理前端的相关内容。


代码放好之后,我们还需要做一些微调,就是当用户已登陆,点击用户的昵称就能跳转到个人中心页面当中。
用户登陆之后,a标注中的内容是“#”号:

所以我们只需要将这个修改为个人中心的路径即可,而这里最好不要放写死的路径,因为关于路径我们经常会变得,所以这里用模板引擎的语法完成路径的设置。
href="{{ url_for('user.user_info') }}"
基本资料实现
我们先来看看基本资料这里有什么问题。
基本资料是个人中心显示的默认内容,而这个地方应该有查询的一步,有数据的显示数据而不应该留空。
还有一个地方是当点击保存之后,应该向后端发送一个请求来更改修改后的相关内容。

那问题来了,我们能不能用一个视图来把之前提到的两种需求都搞定呢?这就是我们接下来要尝试的内容了。
1.处理前端模板相关内容
既然要处理基本资料相关的内容,最后我们肯定是要渲染相关的页面,当来到user.html中查看时,会发现并没有基本资料相关的前端代码,这相关地方只有一个iframe标签:

这个iframe标签其实就是页面里面内嵌一个页面,这个地方内嵌的是user_base_info.html。所以我们将这个文件拖到news当中。
重启项目,然后查看个人中心页面会发现没有找到:

为什么这里更换了文件夹,修改了相关的导入路径会提示没有找到呢?
因为之前这个页面是在static中,所以当项目启动之后我们可以通过路径来访问,而拖动到templates中,则不可以用路径了,我们需要相关的视图函数来完成页面的渲染。
2.定义视图函数
info -> modules -> user -> views.py
.... @user_blue.route('/base_info', methods=['GET', 'POST']) @user_login_data def base_info(): """基本资料""" return render_template("news/user_base_info.html") ....
3.修改前端代码中的链接方式
现在我们已经有了相关的视图函数,接下来就是修改相关的前端代码,而这个地方对a标签的设置,我们按照之前跳转个人中心时使用的方式即可。


{{ url_for('user.base_info') }}
4.完成用户基本内容的获取与渲染
前端页面
修改user_base_info.html中的相关内容。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户中心</title> <link rel="stylesheet" type="text/css" href="../../static/news/css/reset.css"> <link rel="stylesheet" type="text/css" href="../../static/news/css/main.css"> <script type="text/javascript" src="../../static/news/js/jquery-1.12.4.min.js"></script> <script type="text/javascript" src="../../static/news/js/user_base_info.js"></script> </head> <body class="inframe_body"> <form class="base_info"> <h3>基本资料</h3> <div class="form-group"> <label>个性签名:</label> <input id="signature" type="text" name="signature" value="{{ context.user.signature }}" class="input_txt"> </div> <div class="form-group"> <label>用户昵称:</label> <input id="nick_name" type="text" name="" value="{{ context.user.nick_name }}" class="input_txt"> </div> <div class="form-group"> <label>性别:</label> {% if context.user.gender == "MAN" %} <input class="gender" type="radio" name="gender" checked="checked" value="MAN"> <b>男</b> <input class="gender" class="gender" type="radio" name="gender" value="WOMAN"> <b>女</b> {% else %} <input class="gender" type="radio" name="gender" value="MAN"> <b>男</b> <input class="gender" type="radio" name="gender" value="WOMAN" checked="checked"> <b>女</b> {% endif %} </div> <div class="form-group"> <input type="submit" value="保 存" class="input_sub"> </div> </form> </body> </html>
后端逻辑
info -> modules -> user -> views.py
@user_blue.route('/base_info', methods=['GET', 'POST']) @user_login_data def base_info(): """基本资料""" # 获取登录用户信息 user = g.user if not user: return redirect(url_for('index.index')) context = { 'user': user } return render_template("news/user_base_info.html", context=context)
下面是展示的效果:

5.基本资料修改与查询
在前面,我们完成的内容是get请求应该完成的内容,而这个函数中我们要完成的是用一个函数来处理get和post两种请求,所以还需要进行进一步的修改。
后端逻辑
@user_blue.route('/base_info', methods=['GET', 'POST']) @user_login_data def base_info(): """基本资料""" # 1.获取登录用户信息 user = g.user if not user: return redirect(url_for('index.index')) # 2.实现GET请求逻辑 if request.method == 'GET': # 构造渲染数据的上下文 context = { 'user': user } # 渲染界面 return render_template('news/user_base_info.html', context=context) # 3.POST请求逻辑:修改用户基本信息 if request.method == 'POST': # 3.1 获取参数(签名,昵称,性别) nick_name = request.json.get('nick_name') signature = request.json.get('signature') gender = request.json.get('gender') # 3.2 校验参数 if not all([nick_name, signature, gender]): return jsonify(errno=response_code.RET.PARAMERR, errmsg='缺少参数') if gender not in ['MAN', 'WOMAN']: return jsonify(errno=response_code.RET.PARAMERR, errmsg='参数错误') # 3.3 修改用户基本信息 user.signature = signature user.nick_name = nick_name user.gender = gender try: db.session.commit() except Exception as e: db.session.rollback() current_app.logger.error(e) return jsonify(errno=response_code.RET.DBERR, errmsg='修改用户资料失败') # 3.4 "注意":修改了昵称以后,记得将状态保持信息中的昵称页修改 session['nick_name'] = nick_name # 3.5 响应修改资料的结果 return jsonify(errno=response_code.RET.OK, errmsg='修改基本资料成功')
前端逻辑
修改user_base_info.js中的相关内容:
function getCookie(name) { var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); return r ? r[1] : undefined; } $(function () { $(".base_info").submit(function (e) { e.preventDefault() var signature = $("#signature").val() var nick_name = $("#nick_name").val() var gender = $("input[name='gender']:checked").val() if (!nick_name) { alert('请输入昵称') return } if (!gender) { alert('请选择性别') } // TODO 修改用户信息接口 var params = { "signature": signature, "nick_name": nick_name, "gender": gender } $.ajax({ url: "/user/base_info", type: "post", contentType: "application/json", headers: { "X-CSRFToken": getCookie("csrf_token") }, data: JSON.stringify(params), success: function (resp) { 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) } } }) }) })
当设置完成,测试之后会看到下面的运行结果:

我们期望的修改结果是完成三个地方的修改,目前只有两个地方改过来了,而右上角的内容修改需要进行一些小的调试。

在user_base_info.js中,关于保存的ajax的回调函数中会更新父窗口中的内容,而#nick_name会找到id为nick_name的标签进行修改,所以我们需要在用户名显示相关标签中加入id属性。
<a href="{{ url_for('user.user_info') }}" id="nick_name">{{ context.user.nick_name }}</a>
刷新之后发现:

右上角行了,而头像下面的又不行了。
这个是因为在user.html中关于这个用户昵称写死了,所以,改用模板语言来渲染相关的内容:
<div class="user_center_name">{{ context.user.nick_name }}</div>
重启项目,再次刷新网页就好了!
头像设置
关于头像,我们希望在进入这个模块的一开始就从传入前端的user中将默认的头像显示,而选择头像可以从本选择好图片之后点击保存,调用一个视图函数,修改数据库中用户头像的相关信息。

1. 移动前端文件
在设置基本资料时,它用到了专门的html,而关于头像的设置也需要专门的html。将static中的user_pic_info.html移动到指定地方。
2. 基本视图函数及路由配置
后端逻辑
建立相关视图函数来返回头像设置的网页信息。
info -> modules -> user -> views.py
@user_blue.route('/pic_info', methods=['GET', 'POST']) @user_login_data def pic_info(): """设置头像""" # 1.获取登录用户信息 user = g.user if not user: return redirect(url_for('index.index')) # 2.实现GET请求逻辑 if request.method == 'GET': # 构造渲染数据的上下文 context = { 'user': user } # 渲染界面 return render_template('news/user_pic_info.html', context=context) # 3.POST请求逻辑:上传用户头像 if request.method == 'POST': pass
这里,我们还是希望在一个视图函数中完成get和post两种请求的处理。post请求稍后处理,相关位置用pass留空。
前端设置
将前端中相关的静态路径进行修改。
user.html
{{ url_for('user.pic_info') }}
而头像的获取写死了,所以需要进行修改:
user_pic_info.html
<div class="form-group"> <label class="label01">当前图像:</label> <img src="{% if context.user.avatar_url %}{{ context.user.avatar_url }}{% else %}../../static/news/images/user_pic.png{% endif %}" alt="用户图片" class="now_user_pic"> </div>
3.图片存储解决方案

本次,介绍的是利用第三方平台存储服务器来存放相关内容。
七牛云存储
- 对于实际项目的作用:
- 用于在实际项目中存储媒体(图像、音频、视频)文件
- 节省自己服务器空间,节约宽带,提升媒体文件访问的稳定性
- 不需要人力物力对重复数据、冗余数据进行清理及判断
- 官网:https://www.qiniu.com/
- SDK地址:https://developer.qiniu.com/sdk#official-sdk
- 注册,登录,实名认证
不同于容联云通信,使用七牛云存储我们不需要下载sdk,直接可以通过pip 安装对应的工具包。
pip install qiniu
而如果按照提供的文件下载了相关库的话,已经包含了这个库。
封装七牛云
1. 创建文件上传存储工具包
因为七牛云是利用库而不是sdk包,所以,我们只要在工具中创建关于图片上传下载的工具即可。
在info的utils中新建名为file_storage.py的Python文件。
2. 填入测试代码
然后将下面代码放入其中:
# 专门处理文件上传存储的 import qiniu access_key = '' secret_key = '' bucket_name = '' def upload_file(data): """ 上传文件到七牛云 :param data: 要上传的文件的二进制 """ q = qiniu.Auth(access_key, secret_key) token = q.upload_token(bucket_name) ret, info = qiniu.put_data(token, None, data) print(ret['key']) if info.status_code != 200: raise Exception('七牛上传失败') return ret['key'] # # if __name__ == '__main__': # # path = '/Users/zhangjie/Desktop/Images/timg.jpeg' # with open(path, 'rb') as file: # upload_file(file.read())
这个就是封装好的完成七牛云图片上传的工具,要使用它有三个值需要设置,我们来看看这三个值如何获取:
创建存储空间,而存储空间的名字就是bucket_name要设置的内容

拷贝外链默认域名到项目中

拷贝 access_key 和 secret_key 到项目中

3. 分析封装七牛云代码中的关键参数
在代码中,有这么一句:

其实这个地方是完成图片上传并接收到两个返回值,我们通过打断点的形式来看看这两个返回值是什么:

ret通过查看控制台可以看到是一个字典类型的数据,而它里面有一个key,这个可以是七牛云中存储图片的唯一标识,我们可以通过可以来找到指定的图片。
info中存放着一个status_code的信息,这个保存的是一个状态码,如果为200,表示保存成功。
当我们来到七牛云平台查看的时候,会看到ok了:

这个图片的唯一表示有时候页成为指纹,而我们关于图片存储的内容实际上就是一些连接。当然这个连接肯定是看不了的,它还缺少URL中前面的部分,下面图片框中标识的内容就是了。

组合之后就可以获取到这个图片了!这里有一个问题,这个外链我们要和这个指纹拼接在一起然后存储在数据库当中吗?当然是是不会的,因为指纹是不会变的,而外链可能经常发生变化,所以,数据库中只存指纹,而外链我们存储在常量表constants.py中。

这里将相关内容替换成我们自己的外链。
如果使用的是模型类中封装的函数,会发现模型类中会自动完成相关内容的拼接操作:

上传头像前后端逻辑
后端逻辑
info -> modules -> user -> views.py
@user_blue.route('/pic_info', methods=['GET', 'POST']) @user_login_data def pic_info(): """设置头像 """ # 1.获取登录用户信息 user = g.user if not user: return redirect(url_for('index.index')) # 2.实现GET请求逻辑 if request.method == 'GET': # 构造渲染数据的上下文 context = { 'user': user.to_dict() } # 渲染界面 return render_template('news/user_pic_info.html', context=context) # 3.POST请求逻辑:上传用户头像da if request.method == 'POST': # 3.1 获取参数(图片) avatar_file = request.files.get('avatar') # 3.2 校验参数(图片) try: avatar_data = avatar_file.read() except Exception as e: current_app.logger.error(e) return jsonify(errno=response_code.RET.PARAMERR, errmsg='读取头像数据失败') # 3.3 调用上传的方法,将图片上传的七牛 try: key = upload_file(avatar_data) except Exception as e: current_app.logger.error(e) return jsonify(errno=response_code.RET.THIRDERR, errmsg='上传失败') # 3.4 保存用户头像的key到数据库 user.avatar_url = key try: db.session.commit() except Exception as e: db.session.rollback() current_app.logger.error(e) return jsonify(errno=response_code.RET.DBERR, errmsg='保存用户头像失败') data = { 'avatar_url':constants.QINIU_DOMIN_PREFIX + key } # 3.5 响应头像上传的结果 return jsonify(errno=response_code.RET.OK, errmsg='上传成功',data=data)
前端逻辑
user_pic_info.js
function getCookie(name) { var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); return r ? r[1] : undefined; } $(function () { $(".pic_info").submit(function (e) { e.preventDefault(); //TODO 上传头像 $(this).ajaxSubmit({ url: "/user/pic_info", type: "POST", headers: { "X-CSRFToken": getCookie('csrf_token') }, success: function (resp) { if (resp.errno == "0") { $(".now_user_pic").attr("src", resp.data.avatar_url); $(".user_center_pic>img", parent.document).attr("src", resp.data.avatar_url); $(".user_login>img", parent.document).attr("src", resp.data.avatar_url) }else { alert(resp.errmsg) } } }); }); });
如果测试成功会发现三个地方的头像都变化了:

当然了,和基本资料一样,当重新刷新网页的时候,还是会出现些问题,所以需要进一步的处理。
刷新后的问题解决
我们之前讲过,在模型类中,有一个to_dict()方法,可以将模型类所以的内容封装为字典形式的数据,所以这里我们只需要在所有的上下文要传user的地方,调用一下to_dict函数将获取头像的链接进行封装就可以了。
给所有模块中用到头像的地方都调用这个to_dict()函数:
context = { 'user': user.to_dict() }
关于用户中心首页还需要修改一下,因为我们当时并没有对usr.html中头像的相关部分进行修改:
user.html
<img src="{% if context.user.avatar_url %}{{ context.user.avatar_url }}{% else %}../../static/news/images/user_pic.png{% endif %}" alt="用户图片" class="now_user_pic">
如果都设置完成,会发现小奥特曼已经到了需要它的地方:

但是!当我们点击这个退出之后,会发现:

报错了?这是为啥?
这是因为空对象没有to_dict()方法,所以产生这个问题的原因是某个对象为空,但是代码还在调用它的属性和方法。
这个问题产生的原因是在主页和详情中,无论用户登陆或者未登录,都可以去看相关的信息,而用户未登录的时候会赋值为初始值None:

而None并没有to_dict()函数

那怎么解决呢?
很简单,一行解决:
'user': user.to_dict() if user else None,
它等价于下面的代码:
if user: user = user.to_dict() else: user = None

浙公网安备 33010602011771号