Django项目实践(商城):十、个人信息

在这里插入图片描述

(根据居然老师直播课内容整理)

一、用户基本信息

1、用户基本信息逻辑分析

  • 以下是要实现的后端逻辑
    • 用户模型补充email_active字段
    • 查询并渲染用户基本信息
    • 添加邮箱
    • 发送邮箱验证邮件
    • 验证邮箱

2、用户模型补充email_active字段

  • 用户模型中有email字段,但没有邮箱是否激活的字段,需要添加字段
  • 对已存在数据有表中,增加字段时,一般需要添加缺省值
    在这里插入图片描述
email_active = models.BooleanField(default=False, verbose_name='邮箱验证状态')
  • 补充完字段后,需要进行迁移。
python manage.py makemigrations
python manage.py migrate

3、查询用户基本信息

# ./apps/users/views.py 

		context = {
            "username": request.user.username,
            "mobile": request.user.mobile,
            "email": request.user.email,
            "email_active": request.user.email_active,
        }
        return render(request, 'user_center_info.html',context=context)

4、渲染用户基本信息

  • 前端显示用户信息时,有3种方法:
    • ajax 方式查询获取展示
    • django的DTL模板语法方式展示
    • vue 方式展示
  • 为统一前端实现方式,本项目模板中采用vue方式展示
  • 为了保持模板语法一致,将django中模板渲染变量写到js里面,赋值给js变量
  • user_center_info.html:在这里插入图片描述
  • user_center_info.html
    在这里插入图片描述
  • user_center_info.js
    在这里插入图片描述

5、添加邮箱信息

  • 填写邮箱后,用put方式保存邮箱信息

5.1 请求方式

选项 方案
请求方法 PUT
请求地址 /email/

5.2 请求参数:

参数名 类型 是否必传 说明
email string {email:邮箱名}

5.3 响应结果:HTML

字段 说明
邮箱验证失败 响应错误提示
邮箱验证成功 重定向到用户中心

5.4 后端views实现

  • 在users.views.py 中定义一个EmailView类,用put方法保存邮箱名称到用户表中
  • 接收参数:email
    • put方法参数存放在request.body中
    • request.body数据是字节类型的,需要decode()转换成字符串
    • 再转换成json类型
  • 校验邮箱:
    • 正则表达式校验邮箱是否正确
    • 如果不正确,返回错误
  • 保存邮箱信息
    • 正常情况下,用户登录后才会到达此界面,需要校验是用户是否登录(后面再优化)
    • 确认登录后,修改request.user.email属性值并保存,即可完成邮箱信息保存
  • 发送激活邮件(后面再优化)
  • 返回响应结果
# ./apps/users/views.py 
class EmailView(View):
    """添加邮箱"""

    def put(self,request):
        # 接收参数
        # print(request.body) # 建议调试看一下数据及数据类型
        json_str = request.body.decode()
        # print(type(json_str))
        json_dict = json.loads(json_str)
        email = json_dict.get('email')

        # 校验参数
        if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
            return http.HttpResponseForbidden('参数邮箱有误')

        # 存数据
        try:
            request.user.email = email
            request.user.save()
        except Exception as e:
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '添加邮箱失败'})

        # 发送邮件

        # 响应结果
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK'})

5.5 定义路由

	# 保存邮件
    path('email/', views.EmailView.as_view()),

5.6 前端user_center_info.js

  • 点击保存时,执行此js的save_mail函数
  • 首选判断邮箱是否正确,
  • 如果正确,就提交后台
    • 定义url
    • 定义put数据传递方式
    • 用ajax方式发送请求
    • 返回类型是json
      在这里插入图片描述

6、后端判断用户是否登录

6.1 自定义判断用户是否登录的扩展类:返回JSON

  • 获取个人用户中心页面时,写过判断用户是否登录的功能
    • UserInfoView类继续 django的LoginRequiredMixin类和view类
      • LoginRequiredMixin返回的HttpResponseRedirect
        在这里插入图片描述
    • 而put方法返回的是JsonResponse 在这里插入图片描述
  • 所以,继承LoginRequiredMixin类,改写返回方法
    • 将自定义类存放到./utils/views.py 中
    • 重写handle_no_permission方法
# /utils/views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django import http
from utils.response_code import RETCODE


class LoginRequiredJSONMixin(LoginRequiredMixin):
    """自定义判断用户是否登录的扩展类:返回JSON"""

    def handle_no_permission(self):
        """直接响应JSON数据"""
        return http.JsonResponse({'code': RETCODE.SESSIONERR, 'errmsg': '用户未登陆'})

6.2 优化EmailView类

  • 继承自定义判断用户是否登录的扩展类,即可实现用户登录判断
class EmailView(LoginRequiredJSONMixin,View):
	.....

7、发送邮箱验证邮件

7.1 Django发送邮件的配置

7.2 定义和调用发送邮件异步任务

  • 发送邮箱验证邮件是耗时的操作,不能阻塞商城的响应,所以需要异步发送邮件。
  • 我们继续使用Celery实现异步任务。

7.2.1 定义发送邮件异步任务

  • 在celery_tasks应用目录下,创建 email包
  • 定义tasks.py : 完成相关的发送邮件的业务逻辑
    在这里插入图片描述

7.2.2 注册发送邮件异步任务

  • 注册任务:只到包名,系统会自动找tasks.py任务文件
    在这里插入图片描述

7.2.3 加载配置文件

  • celery可以独立与django运行,属不同的进程,所以配置参数不可共用,celery需加载配置参数
    在这里插入图片描述

7.2.4 相关代码实现

  • /celery_tasks/email/tasks.py
from django.core.mail import send_mail
from django.conf import settings
from celery_tasks.main import celery_app


@celery_app.task(name="send_verify_email")
def send_sms_code(email,verify_url):
    '''
    发送邮件的异步任务
    :param email: 发送邮箱
    :param verify_url: 激活链接
    '''
    subject = "商城邮箱验证"
    html_message = '<p>尊敬的用户您好!</p>' \
                   '<p>感谢您使用商城。</p>' \
                   '<p>您的邮箱为:%s 。请点击此链接激活您的邮箱:</p>' \
                   '<p><a href="%s">%s<a></p>' % (email, verify_url, verify_url)

    send_mail(subject, '', from_email=settings.EMAIL_FROM, recipient_list=[email], html_message=html_message)
  • /celery_tasks/main.py
from celery import Celery

import os
# 加载配置文件
if not os.getenv('DJANGO_SETTINGS_MODULE'):
    os.environ['DJANGO_SETTINGS_MODULE'] = 'lgshop.dev'

# 创建celery实例
celery_app = Celery('lg')

# 加载celery配置
celery_app.config_from_object('celery_tasks.config')

# 注册任务
celery_app.autodiscover_tasks(['celery_tasks.sms', 'celery_tasks.email'])

7.2.5 调用异步发送邮件任务

  • /apps/user/views.py
    在这里插入图片描述
    一定要注意:send_verify_email.delay() 而 send_verify_email() 不是异步发送

8、生成邮箱验证链接

  • 邮箱验证链接:http://www.meiduo.site:8000/users/emails/verification/?token=加密后的{‘user_id’: 1, ‘email’: ‘[email protected]’}
  • http://www.meiduo.site:8000/ 应定义为常量,放到配置文件中
  • /users/emails/verification/ 为路由
  • token 为参数变量
  • 加密算法:Django 的itsdangerous加解密
    • 建议将加解密方法写到 utils.py中
  • /lgshop/dev.py
    在这里插入图片描述
  • /apps/users/utils.py
def generate_verify_email_url(user):
    """
        生成邮箱激活链接
        :return: 邮箱激活链接
        http://www.meiduo.site:8000/users/emails/verification/?token=eyJhbGciOiJIUzUxMiIsImlhdCI6MTYxMjUxMzEwMSwiZXhwIjoxNjEyNTk5NTAxfQ.eyJ1c2VyX2lkIjoxLCJlbWFpbCI6IjI3MDUxODU4MzRAcXEuY29tIn0.bNHVCzCjBw2yO3RKqnI3tICLN97xwvKcqFC-qip2XAEpG2hXuCl2vn3E8Q_WxRF0i_z3scqsWokz8rnOV-pxQw
        """
    s = Serializer(settings.SECRET_KEY, constants.VERIFY_EMAIL_TOKEN_EXPIRES)

    data = {'user_id': user.id, 'email': user.email}

    token = s.dumps(data)

    return settings.EMAIL_VERIFY_URL + '?token=' + token.decode()



def check_verify_email_token(token):
    """
    反序列token 获取user
    :param token: 序列化之后的用户信息
    :return: user
    """
    #反序化参数
    s = Serializer(settings.SECRET_KEY, constants.VERIFY_EMAIL_TOKEN_EXPIRES)
    print("s=",type(s),s)

    try:
        data = s.loads(token)
    except:
        return None
    else:
        user_id = data.get('user_id')
        email = data.get('email')
        try:
            user = User.objects.get(id=user_id, email=email)
        except User.DoesNotExist:
            return None
        else:
            return user

  • /apps/users/views.py
    在这里插入图片描述

9、验证激活邮件的后台逻辑

9.1 请求方式

选项 方案
请求方法 GET
请求地址 /emails/verification/

9.2 请求参数:查询参数

参数名 类型 是否必传 说明
token string 邮箱激活链接

9.3 响应结果:HTML

字段 说明
邮箱验证失败 响应错误提示
邮箱验证成功 重定向到用户中心

9.4 veiw实现

  • 获取参数
    • request.GET.get(“token”).decode()
  • 反序列化参数并查询用户
    • user=check_verify_email_token(token)
  • 修改激活状态
    • 如果已激活,提示已激活
    • 如果未激活,修改激活状态
  • 返回响应状态
# /apps/users/views.py
class VerifyEmailView(View):
    """验证邮箱"""

    def get(self, request):
        # 接收参数
        token=request.GET.get("token")

        if not token:
            return http.HttpResponseForbidden('缺少token')

        # 解密 token => user {'user_id': 1, 'email': '[email protected]'}
        # 查询用户
        user=check_verify_email_token(token)
        if user.email_active == 0:
            # 没有激活 email_active 设置为true
            user.email_active = True
            user.save()
        else:
            # email_active 是否已经激活
            return http.HttpResponseForbidden('邮箱已经被激活')

        # 响应结果
        return redirect(reverse('users:info'))

9.5 注册路由

  • /apps/users/url.py
    在这里插入图片描述

二、修改密码

    • 用户点击修改密码后,判断用户是否登录,如果登录,可获取注册页面
      在这里插入图片描述
  • 用户输入原密码和两次新密码后,点击确定提交后端
  • 后端接收数据后,会校验密码:是否符合规定要求、两次新密码是否相同、新旧密码是否一样等
  • 校验通不过,返回错误信息
  • 验证原密码是否正确
  • 保存新密码
  • 清理session, 跳转登录界面

1、接口设计和定义

1.1 请求方式:

选项 方案
请求方法 POST
请求地址 /users/changepassword/

1.2 请求参数:

参数名 类型 是否必传 说明
old_password string 原密码
new_password string 新密码
new_password2 string 确认新密码

1.3 响应结果:html

响应结果 响应内容
change_password_errmsg 错误信息

2、后端view实现

# /apps/users/veiw.py
class ChangePassword(LoginRequiredMixin,View):

    def get(self,request):
        """提供修改密码界面"""
        return render(request, 'user_center_pass.html')

    def post(self,request):
        # 校验参数
        changepassword_form = ChangepasswordForm(request.POST)

        if changepassword_form.is_valid():
            old_passwd = changepassword_form.cleaned_data.get('old_password')
            new_password = changepassword_form.cleaned_data.get('new_password')

            if not request.user.check_password(old_passwd):
                return render(request,'user_center_pass.html', {
    
    'change_password_errmsg':'原始密码错误'})

            try:
                request.user.set_password(new_password)
                request.user.save()
            except Exception as e:
                logger.error(e)
                return render(request, 'user_center_pass.html', {
    
    'change_password_errmsg': '修改密码失败'})

            # 清理状态保持信息
            logout(request)
            response = redirect(reverse('users:login'))
            response.delete_cookie('username')

            # 响应密码修改结果:重定向到登录界面
            return response
        else:
            print(type(changepassword_form.errors.get_json_data()))
            print(changepassword_form.errors.get_json_data())
            print(changepassword_form.errors["__all__"])
            context = {
    
    
                'change_password_errmsg': changepassword_form.errors["__all__"]
            }
            return render(request, 'user_center_pass.html', context=context)
            # return http.HttpResponseForbidden(changepassword_form.errors)
    username = forms.CharField(max_length=20, min_length=5, required=True,
                               error_messages={
    
    "max_length": "用户名长度最长为20", "min_length": "用户名最少长度为5"})
    password = forms.CharField(max_length=20, min_length=8, required=True,
                               error_messages={
    
    "max_length": "密码长度最长为20", "min_length": "密码最少长度为8"})
    remembered = forms.BooleanField(required=False)


class ChangepasswordForm(forms.Form):
    old_password = forms.CharField(max_length=20, min_length=8, required=True,
                               error_messages={
    
    "max_length": "密码长度最长为20", "min_length": "密码最少长度为8",
                                               "required": "密码必须填写"})
    new_password = forms.CharField(max_length=20, min_length=8, required=True,
                                error_messages={
    
    "max_length": "确认密码长度最长为20", "min_length": "确认密码最少长度为8",
                                                "required": "确认密码必须填写"})
    new_password2 = forms.CharField(max_length=20, min_length=8, required=True,
                                error_messages={
    
    "max_length": "确认密码长度最长为20", "min_length": "确认密码最少长度为8",
                                                "required": "确认密码必须填写"})
    def clean(self):
        cleaned_data = super().clean()
        password = cleaned_data.get('new_password')
        password2 = cleaned_data.get('new_password2')
        password3 = cleaned_data.get('old_password')

        if password != password2:
            raise forms.ValidationError('两次密码不一致')
        if password == password3:
            raise forms.ValidationError('输入的新旧密码不能相同')
        return cleaned_data

3、路由定义

在这里插入图片描述

4、前端实现

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/laoluobo76/article/details/113849249