天天看点

Django项目实战——7—(openid是否绑定用户的处理、用户基本信息渲染、添加和验证邮箱)1、openid是否绑定用户的处理2、用户基本信息渲染3、添加和验证邮箱

1、openid是否绑定用户的处理

Django项目实战——7—(openid是否绑定用户的处理、用户基本信息渲染、添加和验证邮箱)1、openid是否绑定用户的处理2、用户基本信息渲染3、添加和验证邮箱

判断openid是否绑定过用户

使用openid查询该QQ用户是否在商城中绑定过用户。

try:
    oauth_user = OAuthQQUser.objects.get(openid=openid)
except OAuthQQUser.DoesNotExist:
    # 如果openid没绑定美多商城用户
    pass
else:
    # 如果openid已绑定美多商城用户
    pass
           

openid已绑定用户的处理

如果openid已绑定美多商城用户,直接生成状态保持信息,登录成功,并重定向到首页。

try:
    oauth_user = OAuthQQUser.objects.get(openid=openid)
except OAuthQQUser.DoesNotExist:
    # 如果openid没绑定商城用户
    pass
else:
    # 如果openid已绑定商城用户
    # 实现状态保持
    qq_user = oauth_user.user
    login(request, qq_user)
    # 响应结果
    next = request.GET.get('state')
    response = redirect(next)
    # 登录时用户名写入到cookie,有效期15天
    response.set_cookie('username', qq_user.username, max_age=3600 * 24 * 15)
    return response
           

openid未绑定用户的处理

  • 为了能够在后续的绑定用户操作中前端可以使用openid,在这里将openid签名后响应给前端。
  • openid属于用户的隐私信息,所以需要将openid签名处理,避免暴露。
try:
    oauth_user = OAuthQQUser.objects.get(openid=openid)
except OAuthQQUser.DoesNotExist:
    # 如果openid没绑定商城用户
    access_token = generate_eccess_token(openid)
    context = {'access_token': access_token}
    return render(request, 'oauth_callback.html', context)
else:
    # 如果openid已绑定商城用户
    # 实现状态保持
    qq_user = oauth_user.user
    login(request, qq_user)
    # 重定向到主页
    response = redirect(reverse('contents:index'))
    # 登录时用户名写入到cookie,有效期15天
    response.set_cookie('username', qq_user.username, max_age=3600 * 24 * 15)
    return response
           
  • oauth_callback.html

    中渲染

    access_token

补充itsdangerous的使用,加密openid的传输q

  • itsdangerous

    模块的参考资料链接

    http://itsdangerous.readthedocs.io/en/latest/

  • 安装:

    pip install itsdangerous

  • TimedJSONWebSignatureSerializer

    的使用

    – 使用

    TimedJSONWebSignatureSerializer

    可以生成带有有效期的token
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from django.conf import settings
# serializer = Serializer(秘钥, 有效期秒)
serializer = Serializer(settings.SECRET_KEY, 300)
# serializer.dumps(数据), 返回bytes类型
token = serializer.dumps({'mobile': '185xxxxxxx78'})
token = token.decode()
# 检验token
# 验证失败,会抛出itsdangerous.BadData异常
serializer = Serializer(settings.SECRET_KEY, 300)
try:
    data = serializer.loads(token)
except BadData:
    return None
           
Django项目实战——7—(openid是否绑定用户的处理、用户基本信息渲染、添加和验证邮箱)1、openid是否绑定用户的处理2、用户基本信息渲染3、添加和验证邮箱

补充:

openid

签名处理

  • oauth.utils.py

def generate_access_token(openid):
    """
    签名openid
    :param openid: 用户的openid
    :return: access_token
    """
    serializer = Serializer(settings.SECRET_KEY, expires_in=constants.ACCESS_TOKEN_EXPIRES)
    data = {'openid': openid}
    token = serializer.dumps(data)   # dumps是序列化操作
    return token.decode()
           

项目实例代码如下:

第三方QQ登陆视图文件

apps/oauth/views.py

"""
第三方QQ登陆视图文件
 apps/oauth/views.py
"""
from django.shortcuts import render, redirect, reverse
from django.views import View
from QQLoginTool.QQtool import OAuthQQ       # 第三方QQ登陆的类
from django.conf import settings
from django import http
from django.http import HttpResponse
from utils.response_code import RETCODE       # 响应代码
from .models import OAuthQQUser
from .utils import generate_access_token, check_access_token   # 加密解密openid封装的方法
from django.contrib.auth import login, logout    # 登录状态保持和退出


# QQ回调用户视图
class QQAuthUserView(View):
    """ 处理QQ登录回调"""
    def get(self, request):
        # 获取code,从url地址上获取
        code = request.GET.get('code')
        if not code:
            return http.HttpResponseForbidden('获取code失败')
        
        # 获取QQ登录页面网址
        oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, client_secret=settings.QQ_CLIENT_SECRET,
                        redirect_uri=settings.QQ_REDIRECT_URI)
        # 网络请求异常处理
        try:
            # 使用code获取access_token
            access_token = oauth.get_access_token(code)
            
            # 通过Access Token获取OpenID
            openid = oauth.get_open_id(access_token)
        except Exception as e:
            return http.HttpResponseServerError('OAUTH2.0认证失败')
        
        # 使用openid 判断该用户是否绑定过商城用户
        # print(openid)  # 唯一
        try:
            oauth_user = OAuthQQUser.objects.get(openid=openid)
        except Exception as e:
            # 没有查询到用户的openid,需要返回一个绑定的页面
            access_token_openid = generate_access_token(openid)       # openid加密
            # 传递到模板中,当做form表单的参数
            context = {"access_token_openid": access_token_openid}    # access_token_openid需要加密处理,可逆的加密,md5不可逆
            return render(request, 'oauth_callback.html', context=context)
        else:
            # 查询到了用户的openid,
            # 状态保持                         # 表示从QQ模型对象中找到对应的用户模型对象
            login(request, oauth_user.user)   # user与oauth_user是外键关系,保持登陆的应该是user,而不是oauth_user
            
            next = request.GET.get('state')   # url中获取'state'字符串的值
            if next != "None":       # next是字符串
                response = redirect(next)     # 跳转至next链接
            else:
                # 跳转到首页
                response = redirect(reverse('contents:index'))
                
            # 保存到cookies,保存14天
            response.set_cookie('username', oauth_user.user.username, max_age=3600*24*14)
            # 响应结果
            return response


# 第三方QQ登陆视图
class QQAuthURLView(View):
    """提供QQ登陆的扫码页面"""
    """
    提供QQ登录页面网址
    https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=xxx&redirect_uri=xxx&state=xxx
    """
    def get(self, request):
        # next表示从哪个页面进入到的登录页面,将来登录成功后,就自动回到那个页面
        next = request.GET.get('next')
        # 获取QQ登录页面网址
        oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, client_secret=settings.QQ_CLIENT_SECRET,
                        redirect_uri=settings.QQ_REDIRECT_URI, state=next)
        # 生成QQ登陆的扫码链接
        login_url = oauth.get_qq_url()
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'login_url': login_url})

           

加密openid操作的方法:

apps/oauth/utils.py

# -*- encoding: utf-8 -*-
"""
@File    : utils.py
@Time    : 2020/8/17 9:45
@Author  : chen

加密openid操作的方法:apps/oauth/utils.py
"""
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from django.conf import settings         # 导入配置文件
from . import constants                  # 导入定义序列化时效


# 加密方法封装,序列化openid
def generate_access_token(openid):
    """
    序列化openid
    :param openid: 用户的openid明文
    :return: access_token密文
    """
    # 创建序列化数据
    serializer = Serializer(settings.SECRET_KEY, expires_in=constants.ACCESS_TOKEN_EXPIRES)
    # 准备数据
    data = {'openid': openid}
    # 数据进行序列化操作
    token = serializer.dumps(data)
    # 返回数据序列化之后的数据
    return token.decode()   # token是字节类型,需要解码


# 反序列化access_token,解密操作
def check_access_token(access_token_openid):
    """
    反序列化
    :param access_token_openid: openid的密文
    :return:openid的明文
    """
    # 创建序列化对象
    serializer = Serializer(settings.SECRET_KEY, expires_in=constants.ACCESS_TOKEN_EXPIRES)
    try:                                                    # 异常处理,防止反序列化失败情况
        # 解密操作,反序列化操作
        data = serializer.loads(access_token_openid)        # data 是字典类型数据
    except Exception as e:
        return None
    return data.get('openid')                           # 查找key返回value值
    
           

第三方QQ登录界面:

templates/oauth_callback.html

{# 第三方QQ登录界面:templates/oauth_callback.html  #}
{% load static %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <title>LG商城-绑定用户</title>
    <link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
	<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
	<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
</head>
<body>
    <div id="app">
    <div class="register_con">
        <div class="l_con fl">
            <a href="{% url 'contents:index' %}" class="reg_logo"><img src="{% static 'images/2.png' %}"></a>
            <div class="reg_slogan">商品美 · 种类多 · 欢迎光临</div>
            <div class="reg_banner"></div>
        </div>
        <div class="r_con fr">
            <div class="reg_title clearfix">
                <h1>绑定用户</h1>
            </div>
            <div class="reg_form clearfix">
                <form method="post" id="reg_form" @submit="on_submit" v-cloak>
                    {% csrf_token %}
                    <ul>
                        <li>
                            <label>手机号:</label>
                            <input type="text" v-model="mobile" @blur="check_mobile" name="mobile" id="phone">
                            <span v-show="error_mobile" class="error_tip">[[ error_mobile_message ]]</span>
                        </li>
                        <li>
                            <label>密码:</label>
                            <input type="password" v-model="password" @blur="check_password" name="password" id="pwd">
                            <span v-show="error_password" class="error_tip">密码最少8位,最长20位</span>
                            {% if account_errmsg %}
                                <span class="error_tip">{{ account_errmsg }}</span>
                            {% endif %}
                        </li>
                        <li>
                            <label>图形验证码:</label>
                            <input type="text" v-model="image_code" @blur="check_image_code" name="image_code" id="pic_code" class="msg_input">
                            <img :src="image_code_url" @click="generate_image_code" alt="图形验证码" class="pic_code">
                            <span v-show="error_image_code" class="error_tip">[[ error_image_code_message ]]</span>
                        </li>
                        <li>
                            <label>短信验证码:</label>
                            <input type="text" v-model="sms_code" @blur="check_sms_code" name="sms_code" id="msg_code" class="msg_input">
                            <a @click="send_sms_code" class="get_msg_code">[[ sms_code_tip ]]</a>
                            <span v-show="error_sms_code" class="error_tip">[[ error_sms_code_message ]]</span>
                            {% if sms_code_errmsg %}
                                <span class="error_tip">{{ sms_code_errmsg }}</span>
                            {% endif %}
                            {% if openid_errmsg %}
                                <span class="error_tip">{{ openid_errmsg }}</span>
                            {% endif %}
                            {% if qq_login_errmsg %}
                                <span class="error_tip">{{ qq_login_errmsg }}</span>
                            {% endif %}
                        </li>

                        <input type="hidden" name="access_token_openid" value="{{ access_token_openid }}">

                        <li class="reg_sub">
                            <input type="submit" value="保 存" name="">
                        </li>
                    </ul>
                </form>
            </div>
        </div>
    </div>
    <div class="footer no-mp">
        <div class="foot_link">
            <a href="#">关于我们</a>
            <span>|</span>
            <a href="#">联系我们</a>
            <span>|</span>
            <a href="#">招聘人才</a>
            <span>|</span>
            <a href="#">友情链接</a>
        </div>
        <p>CopyRight © 2016 北京LG商业股份有限公司 All Rights Reserved</p>
        <p>电话:010-****888    京ICP备*******8号</p>
    </div>
    </div>
    <script type="text/javascript" src="{% static 'js/common.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/oauth_callback.js' %}"></script>
</body>
</html>
           

第三方QQ登录界面静态文件:

static/js/oauth_callback.js

// 第三方QQ登录界面静态文件: static/js/oauth_callback.js
let vm = new Vue({
	el: '#app',
    delimiters: ['[[', ']]'],
	data: {
		mobile: '',
		password: '',
		image_code: '',
		sms_code: '',

		error_mobile: false,
		error_password: false,
		error_image_code: false,
		error_sms_code: false,

		error_mobile_message: '',
		error_image_code_message: '',
		error_sms_code_message: '',

		uuid: '',
		image_code_url: '',
		sms_code_tip: '获取短信验证码',
		sending_flag: false,
	},
	mounted(){
		// 生成图形验证码
		this.generate_image_code();
	},
	methods: {
		// 生成图形验证码
		generate_image_code(){
			this.uuid = generateUUID();
			this.image_code_url = "/image_codes/" + this.uuid + "/";
		},
		// 检查手机号
		check_mobile(){
			let re = /^1[3-9]\d{9}$/;
			if(re.test(this.mobile)) {
				this.error_mobile = false;
			} else {
				this.error_mobile_message = '您输入的手机号格式不正确';
				this.error_mobile = true;
			}
		},
		// 检查密码
		check_password(){
			let re = /^[0-9A-Za-z]{8,20}$/;
			if (re.test(this.password)) {
				this.error_password = false;
			} else {
				this.error_password = true;
			}
		},
		// 检查图片验证码
		check_image_code(){
			if(!this.image_code) {
				this.error_image_code_message = '请填写图片验证码';
				this.error_image_code = true;
			} else {
				this.error_image_code = false;
			}
		},
		// 检查短信验证码
		check_sms_code(){
			if(!this.sms_code){
				this.error_sms_code_message = '请填写短信验证码';
				this.error_sms_code = true;
			} else {
				this.error_sms_code = false;
			}
		},
		// 发送短信验证码
		send_sms_code(){
			// 避免频繁点击发送短信验证码标签
			if (this.sending_flag == true) {
				return;
			}
			this.sending_flag = true;

			// 校验参数
			this.check_mobile();
			this.check_image_code();
			if (this.error_mobile == true || this.error_image_code == true) {
				this.sending_flag = false;
				return;
			}

			// 向后端接口发送请求,让后端发送短信验证码
			let url = '/sms_codes/' + this.mobile + '/?image_code=' + this.image_code+'&uuid='+ this.uuid;
			axios.get(url, {
				responseType: 'json'
			})
				.then(response => {
					// 表示后端发送短信成功
					if (response.data.code == '0') {
						// 倒计时60秒
						let num = 60;
						let t = setInterval(() => {
							if (num == 1) {
								clearInterval(t);
								this.sms_code_tip = '获取短信验证码';
								this.generate_image_code();
								this.sending_flag = false;
							} else {
								num -= 1;
								this.sms_code_tip = num + '秒';
							}
						}, 1000)
					} else {
						if (response.data.code == '4001') {
							this.error_image_code_message = response.data.errmsg;
							this.error_image_code = true;
                        } else { // 4002
							this.error_sms_code_message = response.data.errmsg;
							this.error_sms_code = true;
						}
						this.sending_flag = false;
					}
				})
				.catch(error => {
					console.log(error.response);
					this.sending_flag = false;
				})
		},
		// 绑定openid
		on_submit(){
			this.check_mobile();
			this.check_password();
			this.check_sms_code();

			if(this.error_mobile == true || this.error_password == true || this.error_sms_code == true) {
				// 不满足条件:禁用表单
				window.event.returnValue = false
			}
		}
	}
});
           

第三方登陆QQ的模型文件:

apps/oauth/models.py

"""
第三方登陆QQ的模型文件:apps/oauth/models.py
"""
from django.db import models
from utils.models import BaseModel

# Create your models here.


class OAuthQQUser(BaseModel):
    # ForeignKey('users.User') 外键关联User模型,on_delete=models.CASCADE级联删除
    user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name='用户')
    openid = models.CharField(max_length=100, verbose_name='openid')     # qq用户登录后的id
    
    class Meta:
        db_table = 'tb_oauth_qq'
        verbose_name = 'QQ登陆用户数据'
        verbose_name_plural = verbose_name
           

oauth模块中的路由文件:

apps/oauth/urls.py

# -*- encoding: utf-8 -*-
"""
@File    : urls.py
@Time    : 2020/8/12 22:05
@Author  : chen

oauth模块中的路由文件: apps/oauth/urls.py
"""
from django.urls import path, include
from . import views

app_name = 'oauth'

urlpatterns = [
    path('qq/login/', views.QQAuthURLView.as_view()),        # 定义qq登陆路由   提供QQ登陆的扫码页面
    # 处理QQ登陆后的回调
    path('oauth_callback/', views.QQAuthUserView.as_view()),
]
           

定义oauth模块中的常量:

apps/oauth/constants.py

# -*- encoding: utf-8 -*-
"""
@File    : constants.py
@Time    : 2020/8/17 9:51
@Author  : chen

定义oauth模块中的常量:apps/oauth/constants.py
"""
# 定义oauth模块中TimedJSONWebSignatureSerializer方法的加密时效
ACCESS_TOKEN_EXPIRES = 600
           

openid绑定用户实现

类似于用户注册的业务逻辑

  • 当用户输入的手机号对应的用户已存在

    • 直接将该已存在用户跟openid绑定

  • 当用户输入的手机号对应的用户不存在

    • 新建一个用户,并跟openid绑定

实现绑定用户的逻辑

第三方QQ登陆视图文件:

apps/oauth/views.py

"""
第三方QQ登陆视图文件
 apps/oauth/views.py
"""
from django.shortcuts import render, redirect, reverse
from django.views import View
from QQLoginTool.QQtool import OAuthQQ       # 第三方QQ登陆的类
from django.conf import settings
from django import http
from django.http import HttpResponse
from utils.response_code import RETCODE       # 响应代码
from .models import OAuthQQUser
from .utils import generate_access_token, check_access_token   # 加密解密openid封装的方法
from django.contrib.auth import login, logout    # 登录状态保持和退出
from django_redis import get_redis_connection    # 连接redis
from users.models import User


# QQ回调用户视图
class QQAuthUserView(View):
    """ 处理QQ登录回调"""
    def get(self, request):
        # 获取code,从url地址上获取
        code = request.GET.get('code')
        if not code:
            return http.HttpResponseForbidden('获取code失败')
        
        # 获取QQ登录页面网址
        oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, client_secret=settings.QQ_CLIENT_SECRET,
                        redirect_uri=settings.QQ_REDIRECT_URI)
        # 网络请求异常处理
        try:
            # 使用code获取access_token
            access_token = oauth.get_access_token(code)
            
            # 通过Access Token获取OpenID
            openid = oauth.get_open_id(access_token)
        except Exception as e:
            return http.HttpResponseServerError('OAUTH2.0认证失败')
        
        # 使用openid 判断该用户是否绑定过商城用户
        # print(openid)  # 唯一
        try:
            oauth_user = OAuthQQUser.objects.get(openid=openid)
        except Exception as e:
            # 没有查询到用户的openid,需要返回一个绑定的页面
            access_token_openid = generate_access_token(openid)       # openid加密
            # 传递到模板中,当做form表单的参数
            context = {"access_token_openid": access_token_openid}    # access_token_openid需要加密处理,可逆的加密,md5不可逆
            return render(request, 'oauth_callback.html', context=context)
        else:
            # 查询到了用户的openid,
            # 状态保持                         # 表示从QQ模型对象中找到对应的用户模型对象
            login(request, oauth_user.user)   # user与oauth_user是外键关系,保持登陆的应该是user,而不是oauth_user
            
            next = request.GET.get('state')   # url中获取'state'字符串的值
            if next != "None":       # next是字符串
                response = redirect(next)     # 跳转至next链接
            else:
                # 跳转到首页
                response = redirect(reverse('contents:index'))
                
            # 保存到cookies,保存14天
            response.set_cookie('username', oauth_user.user.username, max_age=3600*24*14)
            # 响应结果
            return response

    def post(self, request):
        """ 实现绑定用户的逻辑 """
        # 接收参数  前端form表单名称要匹配
        # 校验参数
        mobile = request.POST.get('mobile')
        password = request.POST.get('password')
        sms_code_client = request.POST.get('sms_code')  # 用户提交的
        access_token_openid = request.POST.get('access_token_openid')
    
        # 判断短信验证码校验,redis
        redis_conn = get_redis_connection('verify_code')   # 连接验证码数据库
        sms_code_server = redis_conn.get('sms_%s' % mobile)    # 查询方式和存储方式相同
        if sms_code_server is None:
            return render(request, 'oauth_callback.html', {"sms_code_errmsg":"无效的短信验证码!"})
        if sms_code_server.decode() != sms_code_client:     # redis中获取的是字节数据,需要解码
            return render(request, 'oauth_callback', {"sms_code_errmsg":"短信验证码错误!"})
            
        # 判断openid是否有效
        openid = check_access_token(access_token_openid)        # 解密openid
        if not openid:
            return render(request, "oauth_callback.html", {"sms_code_errmsg":"openid已经失效"})
        
        try:
            # 使用手机号查询对应的用户情况
            user = User.objects.get(mobile=mobile)
        except User.DoesNotExist:
            # 用户不存在,新建用户
            user = User.objects.create_user(username=mobile, password=password, mobile=mobile)
        else:
            # 如果用户存在,检查用户密码
            if not user.check_password(password):
                return render(request, 'oauth_callback.html', {"qq_login_errmsg":"账号密码错误"})
        
        try:
            # 将用户绑定openid
            qq_user = OAuthQQUser.objects.create(user=user, openid=openid)
        except Exception as e:
            return render(request, 'oauth_callback.html', {"qq_login_errmsg": "账号密码错误"})
        
        # 实现状态保持   qq用户的状态保持
        login(request, qq_user.user)
        # 响应登录结果   获取url中的信息
        next = request.GET.get('state')
        if next != 'None':
            response = redirect(next)        # 存在,定向到该url
        else:
            response = redirect(reverse('contents:index'))        # 不存在,重定向到首页
        # 存用户信息到cookie
        response.set_cookie('username', qq_user.user.username, max_age=3600*24*14)
        
        return response
        
    
# 第三方QQ登陆视图
class QQAuthURLView(View):
    """提供QQ登陆的扫码页面"""
    """
    提供QQ登录页面网址
    https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=xxx&redirect_uri=xxx&state=xxx
    """
    def get(self, request):
        # next表示从哪个页面进入到的登录页面,将来登录成功后,就自动回到那个页面
        next = request.GET.get('next')
        # 获取QQ登录页面网址
        oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, client_secret=settings.QQ_CLIENT_SECRET,
                        redirect_uri=settings.QQ_REDIRECT_URI, state=next)
        # 生成QQ登陆的扫码链接
        login_url = oauth.get_qq_url()
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'login_url': login_url})

           

2、用户基本信息渲染

用户基本信息逻辑分析

以下是要实现的后端逻辑

  • 用户模型补充

    email_active

    字段
  • 查询并渲染用户基本信息
  • 添加邮箱
  • 发送邮箱验证邮件
  • 验证邮箱

查询并渲染用户基本信息

用户模型补充

email_active

字段

  • 由于在渲染用户基本信息时,需要渲染用户邮箱验证的状态,所以需要给用户模型补充

    email_active

    字段
  • 补充完字段后,需要进行迁移。

-用户模型补充

email_active

字段

"""
模型文件

apps/users/models.py

"""

from django.db import models
from django.contrib.auth.models import AbstractUser


# objects = models.Manager()
class User(AbstractUser):
    """自定义用户模型类"""
    mobile = models.CharField(max_length=11, unique=True, verbose_name="手机号")    # 父类中没有的字段
    # 邮箱是否经过验证的字段   新添加字段
    email_active = models.BooleanField(default=False, verbose_name="邮箱验证的状态")
    
    class Meta:
        db_table = 'tb_users'
        verbose_name = '用户'
        verbose_name_plural = verbose_name
        
    def __str__(self):
        return self.username

           

映射到数据库:

Django项目实战——7—(openid是否绑定用户的处理、用户基本信息渲染、添加和验证邮箱)1、openid是否绑定用户的处理2、用户基本信息渲染3、添加和验证邮箱

渲染用户基本信息

将后端模板数据传递到Vue.js

  • 为了方便实现用户添加邮箱时的界面局部刷新

    – 我们将后端提供的用户数据传入到

    user_center_info.js

// 个人用户中心前端界面静态文件:static/js/user_center_info.js
let vm = new Vue({
    el: '#app',
    delimiters: ['[[', ']]'],
    data: {
        username: username,    // 使用 username: getCookie('username'),    // getCookie方法在common.js文件中定义
        mobile: mobile,
        email: email,
        email_active: email_active,

        set_email: false,
        error_email: false,

        send_email_btn_disabled: false,
        send_email_tip: '重新发送验证邮件',
        histories: [],
    },
    mounted() {
        // 邮箱是否激活:将Python的bool数据转成JS的bool数据
        this.email_active = (this.email_active=='True') ? true : false;
        // 是否在设置邮箱
        this.set_email = (this.email=='') ? true : false;

        // 请求浏览历史记录
        // this.browse_histories();
    },
    methods: {
        // 检查email格式
        check_email(){
            let re = /^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$/;
            if (re.test(this.email)) {
                this.error_email = false;
            } else {
                this.error_email = true;
            }
        },
        // 取消保存
        cancel_email(){
            this.email = '';
            this.error_email = false;
        },
        // 保存email
        save_email(){
            // 检查email格式
            this.check_email();

            if (this.error_email == false) {
                let url = '/emails/';
                axios.put(url, {
                    email: this.email
                }, {
                    headers: {
                        'X-CSRFToken':getCookie('csrftoken')
                    },
                    responseType: 'json'
                })
                    .then(response => {
                        if (response.data.code == '0') {
                            this.set_email = false;
                            this.send_email_btn_disabled = true;
                            this.send_email_tip = '已发送验证邮件';
                        } else if (response.data.code == '4101') {
                            location.href = '/login/?next=/info/';
                        } else {
                            console.log(response);
                        }
                    })
                    .catch(error => {
                        console.log(error.response);
                    });
            }
        },
        // 请求浏览历史记录
        browse_histories(){
            let url = '/browse_histories/';
            axios.get(url, {
                responseType: 'json'
            })
                .then(response => {
                    this.histories = response.data.skus;
                    for(let i=0; i<this.histories.length; i++){
                        this.histories[i].url = '/detail/' + this.histories[i].id + '/';
                    }
                })
                .catch(error => {
                    console.log(error.response);
                })
        },
    }
});
           

个人用户中心前端界面文件:

templates/user_center_info.html

{# 个人用户中心前端界面文件:templates/user_center_info.html #}

{% load static %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<title>LG商城-用户中心</title>
	<link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
	<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
    <script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
</head>
<body>
	<div id="app">
	<div class="header_con">
		<div class="header" v-cloak>
            <div class="welcome fl">欢迎来到LG商城!</div>
            <div class="fr">
                <div v-if="username" class="login_btn fl">
                    欢迎您:<em>[[ username ]]</em>
                    <span>|</span>
                    <a href="{% url 'users:logout' %}">退出</a>
                </div>
                <div v-else class="login_btn fl">
                    <a href="{% url 'users:login' %}">登录</a>
                    <span>|</span>
                    <a href="{% url 'users:register' %}">注册</a>
                </div>
                <div class="user_link fl">
                    <span>|</span>
                    <a href="{% url 'users:info' %}">用户中心</a>
                    <span>|</span>
                    <a href="cart.html">我的购物车</a>
                    <span>|</span>
                    <a href="user_center_order.html">我的订单</a>
                </div>
            </div>
        </div>
	</div>
	<div class="search_bar clearfix">
        {#    contents命名空间    #}
		<a href="{% url 'contents:index' %}" class="logo fl"><img src="{% static 'images/1.png' %}"></a>
		<div class="search_wrap fl">
			<form method="get" action="/search/" class="search_con">
                <input type="text" class="input_text fl" name="q" placeholder="搜索商品">
                <input type="submit" class="input_btn fr" name="" value="搜索">
            </form>
			<ul class="search_suggest fl">
				<li><a href="#">索尼微单</a></li>
				<li><a href="#">优惠15元</a></li>
				<li><a href="#">美妆个护</a></li>
				<li><a href="#">买2免1</a></li>
			</ul>
		</div>
	</div>
	<div class="main_con clearfix">
		<div class="left_menu_con clearfix">
			<h3>用户中心</h3>
			<ul>
				<li><a href="{% url 'users:info' %}" class="active">· 个人信息</a></li>
				<li><a href="">· 收货地址</a></li>
                <li><a href="user_center_order.html">· 全部订单</a></li>
				<li><a href="user_center_pass.html">· 修改密码</a></li>
			</ul>
		</div>
		<div class="right_content clearfix" v-cloak>
             <div class="info_con clearfix">
                <h3 class="common_title2">基本信息</h3>
                <ul class="user_info_list">
{# 使用后端的传输数据信息    apps/users/views.py文件中的context信息    另一种获取信息方式是从cookie中获取,使用 username: getCookie('username'),    // getCookie方法在common.js文件中定义 #}
{#                    <li><span>用户名:</span>{{ username }}</li>#}
{#                    <li><span>联系方式:</span>{{ mobile }}</li>#}
                    {#     上面是Django的语法形式,下面语句是Vue的形式               #}
                    <li><span>用户名:</span>[[ username ]]</li>
                    <li><span>联系方式:</span>[[ mobile ]]</li>
                    <li>
                        <span>Email:</span>
                        <div v-if="set_email">
                            <input v-model="email" @blur="check_email" type="email" name="email" class="email">
                            <input @click="save_email" type="button" name="" value="保 存">
                            <input @click="cancel_email" type="reset" name="" value="取 消">
                            <div v-show="error_email" class="error_email_tip">邮箱格式错误</div>
                        </div>
                        <div v-else>
                            <input v-model="email" type="email" name="email" class="email" readonly>
                            <div v-if="email_active">
                                已验证
                            </div>
                            <div v-else>
                                待验证<input @click="save_email" :disabled="send_email_btn_disabled" type="button" :value="send_email_tip">
                            </div>
                        </div>
                    </li>
                </ul>
            </div>
            <h3 class="common_title2">最近浏览</h3>
            <div class="has_view_list" v-cloak>
                <ul class="goods_type_list clearfix">
                    <li v-for="sku in histories">
                        <a :href="sku.url"><img :src="sku.default_image_url"></a>
                        <h4><a :href="sku.url">[[ sku.name ]]</a></h4>
                        <div class="operate">
                            <span class="price">¥[[ sku.price ]]</span>
                            <span class="unit">台</span>
                            <a href="javascript:;" class="add_goods" title="加入购物车"></a>
                        </div>
                    </li>
                </ul>
            </div>
		</div>
	</div>
	<div class="footer">
		<div class="foot_link">
			<a href="#">关于我们</a>
			<span>|</span>
			<a href="#">联系我们</a>
			<span>|</span>
			<a href="#">招聘人才</a>
			<span>|</span>
			<a href="#">友情链接</a>
		</div>
		<p>CopyRight © 2016 北京LG商业股份有限公司 All Rights Reserved</p>
		<p>电话:010-****888    京ICP备*******8号</p>
	</div>
	</div>
{#    将后端Django传输的数据转换成Vue能够采用的语法形式 #}
    <script type="text/javascript">
        let username = "{{ username }}";
        let mobile = "{{ mobile }}";
        let email = "{{ email }}";
        let email_active = "{{ email_active }}";
    </script>
    <script type="text/javascript" src="{% static 'js/common.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/user_center_info.js' %}"></script>
</body>
</html>
           

视图文件

apps/users/views.py

文件,用户后端验证视图文件

"""
视图文件

apps/users/views.py文件,用户后端验证视图文件
"""
from django.shortcuts import render, redirect, reverse
from django.http import HttpResponse, JsonResponse
from django.views import View
from .forms import RegisterForm, LoginForm
from .models import User
from django.contrib.auth import login, logout, authenticate    # authenticate封装的验证用户名和密码是否正确的方法
from django_redis import get_redis_connection
from django.contrib.auth.mixins import LoginRequiredMixin      # 验证用户是否登录的类


# 个人用户中心
class UserInfoView(LoginRequiredMixin, View):
    """用户个人中心"""
    def get(self, request):
        """提供用户个人中心"""
        '''
        login_url = None
        permission_denied_message = ''
        raise_exception = False
        redirect_field_name = REDIRECT_FIELD_NAME
        '''
        # 验证用户是否登陆
        # if request.user.is_authenticated:
        #     return render(request, 'user_center_info.html')
        # else:
        #     return redirect(reverse('users:login'))           # 用户未登录,跳转至登陆界面
        # print(request.user)
        # print(request.user.username)
        # print(request.user.mobile)
        
        # 数据由Django后端来提供,前端数据的读取方式采用Vue方式读取[[ username ]]
        context = {
            'username': request.user.username,
            'mobile': request.user.mobile,
        }
        
        # 上面的代码后期需要复用多次,可以引入LoginRequiredMixin类封装的方法和REDIRECT_FIELD_NAME = 'next'参数来重定向
        return render(request, 'user_center_info.html', context=context)        # 重定向到个人中心


# 退出登录
class LogoutView(View):
    """退出登陆逻辑实现"""
    def get(self, request):
        """实现用户退出登录的功能"""
        # 清除状态保持信息
        logout(request)
        
        # 退出登录之后重定向到首页
        response = redirect(reverse('contents:index'))
        
        # 删除cookies中的用户名
        # result.set_cookie('username', user.username, max_age=3600*24*14)    # 保存两周
        response.delete_cookie('username')
        return response                           # 响应结果
    

# 用户登陆
class LoginView(View):
    """用户名登陆"""
    def get(self, request):
        """  提供登陆界面
        :return: 登陆界面
        """
        return render(request, 'login.html')

    def post(self, request):
        """
        实现登录逻辑
        :param request: 请求对象
        :return: 登录结果
        """
        # 接受参数
        login_form = LoginForm(request.POST)
        
        # 校验参数
        if login_form.is_valid():
            # 接收参数
            username = login_form.cleaned_data.get('username')
            password = login_form.cleaned_data.get('password')
            remembered = request.POST.get('remembered')                 # 没经过form验证,使用request接收参数

            # 认证登录用户
            # users = User.objects.get(username=username)
            # users.check_password(password)                            # check_password验证密码封装的方法,返回值bool类型
            """  authenticate方法源码
             def authenticate(self, request, username=None, password=None, **kwargs):
                if username is None:
                    username = kwargs.get(UserModel.USERNAME_FIELD)
                try:
                    user = UserModel._default_manager.get_by_natural_key(username)
                except UserModel.DoesNotExist:
                    # Run the default password hasher once to reduce the timing
                    # difference between an existing and a nonexistent user (#20760).
                    UserModel().set_password(password)
                else:
                    if user.check_password(password) and self.user_can_authenticate(user):
                        return user
            """
            user = authenticate(username=username, password=password)   # 重构authenticate方法之后,可以验证手机号登录和用户名登录
            if user is None:
                # 用户名或者密码输入错误
                return render(request, 'login.html', {"errmsg": "用户名或者密码输入错误"})
            
            # 实现状态保持
            login(request, user)

            # 设置状态保持的周期
            if remembered != 'on':
                # 没选中记住密码,浏览器关闭就需要销毁session信息
                request.session.set_expiry(0)                  # set_expiry过期时间
            else:
                # 选中记住密码,session信息默认保存两周
                # request.session.set_expiry(60*60*24*14)
                request.session.set_expiry(None)
            
            # REDIRECT_FIELD_NAME = 'next'      LoginRequiredMixin类中源码的参数 ,用于获取登陆前的路由请求,方便登陆后直接定向到之前的请求界面
            next = request.GET.get('next')       # 获取url中的‘next’字符串参数
            if next:
                result = redirect(next)          # 如果存在next参数,则重定向到这个地址
            else:
                # 后端将用户信息存入cookie
                result = redirect(reverse('contents:index'))            # redirect方法源码中会返回一个redirect_class
            # set_cookie('key', 'value', 'erpriy')   erpriy过期时间
            result.set_cookie('username', user.username, max_age=3600*24*14)    # 保存两周
            
            # 响应登录结果    跳转到首页
            return result
        else:
            print(login_form.errors.get_json_data())
            context = {
                "form_errors": login_form.errors,
            }
            return render(request, 'login.html', context=context)
    

class RegisterView(View):
    """用户注册"""
    def get(self, request):
        """提供用户的注册界面"""
        return render(request, 'register.html')
    
    def post(self, request):
        """提供用户的注册逻辑"""
        # 前端用户提交数据
        form = RegisterForm(request.POST)
        if form.is_valid():
            # 接收参数
            username = form.cleaned_data.get('username')
            password = form.cleaned_data.get('password')
            mobile = form.cleaned_data.get('mobile')
            sms_code_client = request.POST.get('sms_code')         # 验证短信验证码  sms_code是register.html 文件中命名的
            
            # 判断用户输入的短信验证码是否正确
            redis_conn = get_redis_connection('verify_code')       # 链接redis中配置的数据库
            sms_code_server = redis_conn.get('sms_%s' % mobile)    # 根据存储时候的格式写
            if sms_code_server is None:
                return render(request, 'register.html', {'sms_code_errmsg': '短信验证码已经失效'})     # 错误信息渲染到前端界面
            if sms_code_server.decode() != sms_code_client:       # sms_code_server数据类型需要转换
                return render(request, 'register.html', {'sms_code_errmsg': '短信验证码填写错误'})
            
            try:
                # user = User(username=username, password=password, mobile=mobile)
                # 下面的添加数据的方法是封装了加密等功能的函数,更安全
                users = User.objects.create_user(username=username, password=password, mobile=mobile)
            except:    # 如果保存数据失败
                return render(request, 'register.html', {'register_error_message': '注册失败'})
            
            # 保持用户登录的状态
            login(request, users)
            
            # 返回响应
            # return HttpResponse('success')
            return redirect(reverse('contents:index'))           # 注册成功,跳转到首页
        else:
            print(form.errors.get_json_data())
            # return HttpResponse("fail")
            # 返回注册错误信息到前端界面
            context = {
                'form_error': form.errors,
            }
            return render(request, 'register.html', context=context)
    

# 判断用户名是否已经存在
class UsernameExists(View):
    """ 判断用户名是否已经存在"""
    def get(self, request, username):     # username用户名
        count = User.objects.filter(username=username).count()      # 查询数据库中信息
        return JsonResponse({"code": 0, "errmsg": "OK", "count": count})   # 返回给前端界面
    
           

3、添加和验证邮箱

添加邮箱后端逻辑

添加邮箱接口设计和定义

请求方式

Django项目实战——7—(openid是否绑定用户的处理、用户基本信息渲染、添加和验证邮箱)1、openid是否绑定用户的处理2、用户基本信息渲染3、添加和验证邮箱

请求参数

Django项目实战——7—(openid是否绑定用户的处理、用户基本信息渲染、添加和验证邮箱)1、openid是否绑定用户的处理2、用户基本信息渲染3、添加和验证邮箱

响应结果:JSON

Django项目实战——7—(openid是否绑定用户的处理、用户基本信息渲染、添加和验证邮箱)1、openid是否绑定用户的处理2、用户基本信息渲染3、添加和验证邮箱

添加邮箱后端逻辑实现

用户邮箱绑定:

apps/users/views.py

文件

"""
视图文件

apps/users/views.py文件,用户后端验证视图文件
"""
from django.shortcuts import render, redirect, reverse
from django.http import HttpResponse, JsonResponse
from django.views import View
from .forms import RegisterForm, LoginForm
from .models import User
from django.contrib.auth import login, logout, authenticate    # authenticate封装的验证用户名和密码是否正确的方法
from django_redis import get_redis_connection
from django.contrib.auth.mixins import LoginRequiredMixin      # 验证用户是否登录的类
import json
from django.http import QueryDict      # 转换数据类型


# 用户邮箱绑定
class EmailView(View):
    """添加邮箱"""
    # 此时是put请求,一般用于更新数据
    def put(self, request):
        """添加邮箱的业务逻辑实现 """
        '''
        put 提交的数据是在body属性中,是字节数据类型Bytes
        get,post提交的数据是QueryDict的数据类型
        '''
        print(request.body.decode())
        print(type(request.body.decode()))    # str数据类型
        # 从request中获取email
        email = json.loads(request.body.decode()).get('email')
        print(email)
        
        return HttpResponse('success')


# 个人用户中心
class UserInfoView(LoginRequiredMixin, View):
    """用户个人中心"""
    def get(self, request):
        """提供用户个人中心"""
        '''
        login_url = None
        permission_denied_message = ''
        raise_exception = False
        redirect_field_name = REDIRECT_FIELD_NAME
        '''
        # 验证用户是否登陆
        # if request.user.is_authenticated:
        #     return render(request, 'user_center_info.html')
        # else:
        #     return redirect(reverse('users:login'))           # 用户未登录,跳转至登陆界面
        # print(request.user)
        # print(request.user.username)
        # print(request.user.mobile)
        
        # 数据由Django后端来提供,前端数据的读取方式采用Vue方式读取[[ username ]]
        context = {
            'username': request.user.username,
            'mobile': request.user.mobile,
        }
        
        # 上面的代码后期需要复用多次,可以引入LoginRequiredMixin类封装的方法和REDIRECT_FIELD_NAME = 'next'参数来重定向
        return render(request, 'user_center_info.html', context=context)        # 重定向到个人中心


# 退出登录
class LogoutView(View):
    """退出登陆逻辑实现"""
    def get(self, request):
        """实现用户退出登录的功能"""
        # 清除状态保持信息
        logout(request)
        
        # 退出登录之后重定向到首页
        response = redirect(reverse('contents:index'))
        
        # 删除cookies中的用户名
        # result.set_cookie('username', user.username, max_age=3600*24*14)    # 保存两周
        response.delete_cookie('username')
        return response                           # 响应结果
    

# 用户登陆
class LoginView(View):
    """用户名登陆"""
    def get(self, request):
        """  提供登陆界面
        :return: 登陆界面
        """
        return render(request, 'login.html')

    def post(self, request):
        """
        实现登录逻辑
        :param request: 请求对象
        :return: 登录结果
        """
        # 接受参数
        login_form = LoginForm(request.POST)
        
        # 校验参数
        if login_form.is_valid():
            # 接收参数
            username = login_form.cleaned_data.get('username')
            password = login_form.cleaned_data.get('password')
            remembered = request.POST.get('remembered')                 # 没经过form验证,使用request接收参数

            # 认证登录用户
            # users = User.objects.get(username=username)
            # users.check_password(password)                            # check_password验证密码封装的方法,返回值bool类型
            """  authenticate方法源码
             def authenticate(self, request, username=None, password=None, **kwargs):
                if username is None:
                    username = kwargs.get(UserModel.USERNAME_FIELD)
                try:
                    user = UserModel._default_manager.get_by_natural_key(username)
                except UserModel.DoesNotExist:
                    # Run the default password hasher once to reduce the timing
                    # difference between an existing and a nonexistent user (#20760).
                    UserModel().set_password(password)
                else:
                    if user.check_password(password) and self.user_can_authenticate(user):
                        return user
            """
            user = authenticate(username=username, password=password)   # 重构authenticate方法之后,可以验证手机号登录和用户名登录
            if user is None:
                # 用户名或者密码输入错误
                return render(request, 'login.html', {"errmsg": "用户名或者密码输入错误"})
            
            # 实现状态保持
            login(request, user)

            # 设置状态保持的周期
            if remembered != 'on':
                # 没选中记住密码,浏览器关闭就需要销毁session信息
                request.session.set_expiry(0)                  # set_expiry过期时间
            else:
                # 选中记住密码,session信息默认保存两周
                # request.session.set_expiry(60*60*24*14)
                request.session.set_expiry(None)
            
            # REDIRECT_FIELD_NAME = 'next'      LoginRequiredMixin类中源码的参数 ,用于获取登陆前的路由请求,方便登陆后直接定向到之前的请求界面
            next = request.GET.get('next')       # 获取url中的‘next’字符串参数
            if next:
                result = redirect(next)          # 如果存在next参数,则重定向到这个地址
            else:
                # 后端将用户信息存入cookie
                result = redirect(reverse('contents:index'))            # redirect方法源码中会返回一个redirect_class
            # set_cookie('key', 'value', 'erpriy')   erpriy过期时间
            result.set_cookie('username', user.username, max_age=3600*24*14)    # 保存两周
            
            # 响应登录结果    跳转到首页
            return result
        else:
            print(login_form.errors.get_json_data())
            context = {
                "form_errors": login_form.errors,
            }
            return render(request, 'login.html', context=context)
    

# 用户注册
class RegisterView(View):
    """用户注册"""
    def get(self, request):
        """提供用户的注册界面"""
        return render(request, 'register.html')
    
    def post(self, request):
        """提供用户的注册逻辑"""
        # 前端用户提交数据
        form = RegisterForm(request.POST)
        if form.is_valid():
            # 接收参数
            username = form.cleaned_data.get('username')
            password = form.cleaned_data.get('password')
            mobile = form.cleaned_data.get('mobile')
            sms_code_client = request.POST.get('sms_code')         # 验证短信验证码  sms_code是register.html 文件中命名的
            
            # 判断用户输入的短信验证码是否正确
            redis_conn = get_redis_connection('verify_code')       # 链接redis中配置的数据库
            sms_code_server = redis_conn.get('sms_%s' % mobile)    # 根据存储时候的格式写
            if sms_code_server is None:
                return render(request, 'register.html', {'sms_code_errmsg': '短信验证码已经失效'})     # 错误信息渲染到前端界面
            if sms_code_server.decode() != sms_code_client:       # sms_code_server数据类型需要转换
                return render(request, 'register.html', {'sms_code_errmsg': '短信验证码填写错误'})
            
            try:
                # user = User(username=username, password=password, mobile=mobile)
                # 下面的添加数据的方法是封装了加密等功能的函数,更安全
                users = User.objects.create_user(username=username, password=password, mobile=mobile)
            except:    # 如果保存数据失败
                return render(request, 'register.html', {'register_error_message': '注册失败'})
            
            # 保持用户登录的状态
            login(request, users)
            
            # 返回响应
            # return HttpResponse('success')
            return redirect(reverse('contents:index'))           # 注册成功,跳转到首页
        else:
            print(form.errors.get_json_data())
            # return HttpResponse("fail")
            # 返回注册错误信息到前端界面
            context = {
                'form_error': form.errors,
            }
            return render(request, 'register.html', context=context)
    

# 判断用户名是否已经存在
class UsernameExists(View):
    """ 判断用户名是否已经存在"""
    def get(self, request, username):     # username用户名
        count = User.objects.filter(username=username).count()      # 查询数据库中信息
        return JsonResponse({"code": 0, "errmsg": "OK", "count": count})   # 返回给前端界面
    

           

个人用户中心前端界面静态文件:

static/js/user_center_info.js

// 个人用户中心前端界面静态文件:static/js/user_center_info.js
let vm = new Vue({
    el: '#app',
    delimiters: ['[[', ']]'],
    data: {
        username: username,
        mobile: mobile,
        email: email,
        email_active: email_active,

        set_email: false,
        error_email: false,

        send_email_btn_disabled: false,
        send_email_tip: '重新发送验证邮件',
        histories: [],
    },
    mounted() {
        // 邮箱是否激活:将Python的bool数据转成JS的bool数据
        this.email_active = (this.email_active=='True') ? true : false;
        // 是否在设置邮箱
        this.set_email = (this.email=='') ? true : false;

        // 请求浏览历史记录
        // this.browse_histories();
    },
    methods: {
        // 检查email格式     定义方法
        check_email(){
            let re = /^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$/;  // 邮箱正则匹配
            if (re.test(this.email)) {
                this.error_email = false;
            } else {
                this.error_email = true;
            }
        },
        // 取消保存
        cancel_email(){
            this.email = '';
            this.error_email = false;
        },
        // 保存email
        save_email(){
            // 检查email格式
            this.check_email();

            if (this.error_email == false) {
                let url = '/users/emails/';          // 路由  注意前面的/users/不能忘
                axios.put(url, {                     // put 请求
                    email: this.email
                }, {
                    headers: {
                        'X-CSRFToken':getCookie('csrftoken')    // 需要带上cookie中的csrftoken
                    },
                    responseType: 'json'
                })
                    .then(response => {                                   // 成功回调的情况
                        if (response.data.code == '0') {                 // 响应成功  代码 0
                            this.set_email = false;
                            this.send_email_btn_disabled = true;
                            this.send_email_tip = '已发送验证邮件';
                        } else if (response.data.code == '4101') {        // 响应失败  代码 4101
                            location.href = '/users/login/?next=/info/';        // 跳转到该路由  登录界面
                        } else {
                            console.log(response);                         // 异常打印错误
                        }
                    })
                    .catch(error => {                                     // 回调失败情况
                        console.log(error.response);                      // 打印错误信息
                    });
            }
        },
        // 请求浏览历史记录
        browse_histories(){
            let url = '/browse_histories/';
            axios.get(url, {
                responseType: 'json'
            })
                .then(response => {
                    this.histories = response.data.skus;
                    for(let i=0; i<this.histories.length; i++){
                        this.histories[i].url = '/detail/' + this.histories[i].id + '/';
                    }
                })
                .catch(error => {
                    console.log(error.response);
                })
        },
    }
});
           

页面测试正常:

Django项目实战——7—(openid是否绑定用户的处理、用户基本信息渲染、添加和验证邮箱)1、openid是否绑定用户的处理2、用户基本信息渲染3、添加和验证邮箱