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中,则不可以用路径了,我们需要相关的视图函数来完成页面的渲染。
View Code

 

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>&nbsp;&nbsp;&nbsp;
                <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)
                }
            }
        })
    })
})
View Code

 

当设置完成,测试之后会看到下面的运行结果:

 

 

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

 在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    
posted @ 2019-11-27 10:13  苦行僧95  阅读(97)  评论(0)    收藏  举报