Django项目实践(商城):八、用户登录

一、用户名登录
1、用户名登录逻辑分析

- 用户输入地址后,获取登录页面
- 输入必要信息用后,点击登录,向后端发送登录请求
- 后端接收请求,提取参数,然后校验参数,如果有误,向前端返回错误信息
- 认证参数:验证用户名和密码是数据库信息比较
- 如果正确,保持状态
- 将登录信息保存到session中,即redis中
- 向前端返回登录结果
2、用户名登录接口设计
2.1 请求方式
| 选项 | 方案 |
|---|---|
| 请求方法 | POST |
| 请求地址 | /login/ |
2.2 请求参数:表单
| 参数名 | 类型 | 是否必传 | 说明 |
|---|---|---|---|
| username | string | 是 | 用户名 |
| password | string | 是 | 密码 |
| remembered | string | 否 | 是否记住用户 |
2.3 响应结果:HTML
| 字段 | 说明 |
|---|---|
| 登录失败 | 响应错误提示 |
| 登录成功 | 重定向到首页 |
3、用户名登录接口定义
3.1 用户注册接口定义
class LoginView(View):"""用户名登录"""def get(self, request):"""提供登录界面:param request: 请求对象:return: 登录界面"""return render(request, 'login.html')def post(self, request):"""实现登录逻辑:param request: 请求对象:return: 登录结果"""pass
3.2 定义校验处理类
- 在./apps/users下定义forms.py,对需要校验的数据进行校验处理
- 对数据进行简单校验:变量名(传递的参数名)=form.CharField(简单校验类型=标准值,错误信息字典)
- 用户名、密码、是否记住登录进行校验
- 无需其它校验,所以不用clean()方法
class LoginForm(forms.Form):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)
3.3 用户登录接口开发
- 接收并校验参数:实例化校验处理类
- 判断校验结果
- 如果校验无误:
- 取出 用户名、密码、是否记住登录
- 判断 必传参数是否传入all([参数1,参数2,…]),如果为否,返回缺少参数错误
- 认证登录用户:
- 一般作法:
- 根据users模型从SQL数据库取出登录用户名的相关信息
- 进行密码时加密并校验密码
- 根据结果、判断、或出错,进行错误返回
- Django提供了django.contrib.auth.authenticate()方法,
- 根据登录用户名、密码,返回一个users对象,如果用户名或密码错误,返回None
- 如果None,返回错误信息
- 一般作法:
- 保持状态:利用django提供的方法: login(request, user)
- 根据 是否记住登录 的值 设置 session 保存时间:request.session.set_expiry()
- 返回到首页
class LoginView(View):"""用户名登录"""def get(self, request):"""提供登录页面:return: 登录页面"""return render(request, 'login.html')def post(self, 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 = login_form.cleaned_data.get('remembered')if not all([username, password]):return http.HttpResponseForbidden('缺少必传参数')# 认证登录用户# user = User.objects.get(username=username)# pwd = user.password # 密文# user.check_password()# if check_password(password, pwd):# print('密码正确')# else:# print('密码错误')user = authenticate(username=username, password=password)# print(user)if user is None:return render(request, 'login.html', {'account_errmsg': '账号或密码错误'})# 状态保持login(request, user)if remembered != True:# 没有记住登录 浏览器关闭就销毁request.session.set_expiry(0)else:# 记住登录 状态保持默认为2周request.session.set_expiry(None)# 响应结果 重定向到首页return redirect(reverse('contents:index'))else:# print(login_form.errors)# print(login_form.errors.get_json_data())context = {'forms_errors': login_form.errors}return render(request, 'login.html', context=context)
4、路由定义
- users 主路由已定义,只需定义子路由

# /apps/usrs/urls.py path('login/', views.LoginView.as_view(), name='login'),
5、前端代码、vue及js (略)
二、多账号登录(用户名或手机均可登录)
-
多账号登录:
-
Django自带的用户认证后端默认是使用用户名实现用户认证的。
-
用户认证后端位置:django.contrib.auth.backends.ModelBackend。
-
如果想实现用户名和手机号都可以认证用户,就需要自定义用户认证后端。
-
自定义用户认证后端步骤
1、在users应用中新建utils.py文件
- 创建到其它地方也可以

2、新建类UsernameMobileBackend,继承自ModelBackend

3、重写authenticate()方法
3.1 django.contrib.auth.backends.ModelBackend的authenticate()方法
- 查询得到user对象
- 根据参数username查询用户信息,即通过username字段,得到user对象
- 如果查询到用户,需要校验密码
- 密码校验正确,返回user

3.2 查询得到user对象
- 参数username可能传的用户名,也可能传的是手机号
- 通过正则表达式可以判断参数username 是否是手机号
- 如果是手机号,则通过 moblie=username 查询用户信息
- 如果不是,则通过 username=username 查询用户信息
- 因数据库查询可能出现异常,需要异常处理
- 如果出现异常,user对象就为None
- 如果正常,就得到user对象
- 此代码与后面功能没有关联性,可以封装成方法
- 一般方法定义在类里面,但此类是继承自ModelBackend,因不清楚ModelBackend类里面有什么方法,不建议放在类里面,
3.3 如果查询到用户,校验密码
- 当user对象存在,并且密码正确时,返回user 对象,否则返回None
3.4 完整代码
from django.contrib.auth.backends import ModelBackend
import re
from apps.users.models import Userdef get_user_by_account(account):"""获取user对象:param account: 用户名或者手机号:return: user"""try:if re.match(r'^1[3-9]\d{9}', account):user = User.objects.get(mobile=account)else:user = User.objects.get(username=account)except:return Noneelse:return userclass UsernameMobileBackend(ModelBackend):def authenticate(self, request, username=None, password=None, **kwargs):"""重写认证方法:param username: 用户名或者手机号:param password: 密码明文:param kwargs: 额外参数:return: user"""# 使用账号查询用户# 如果查询到用户,需要校验密码# 密码校验成功 返回useruser= get_user_by_account(username)if user and user.check_password(password):return userelse:return None
4 配置自定义用户认证后端
4.1Django自带认证后端源码

4.2 配置自定义用户认证后端
- 在项目参数配置文件中,重新定义 AUTHENTICATION_BACKENDS 值
# /lgshop/dev.py
# 指定自定义的用户认证后端
AUTHENTICATION_BACKENDS = ['users.utils.UsernameMobileBackend']
5、前端界面(略)
三、首页用户名展示方案
1、方案一:模板中 request 变量直接渲染用户名
- 缺点:不方便做首页静态化
1.1 index.html代码:
- 判断 user.is_authenticated属性,
- 如果true ,显示 用户名和退出链接
- 如果false,显示登录 和 注册链接

{% if user.is_authenticated %}<div class="login_btn fl">欢迎您:<em>{{ user.username }}</em><span>|</span><a href="#">退出</a></div>{% else %}<div class="login_btn fl"><a href="{% url 'users:login' %}" >登录</a><span>|</span><a href="{% url 'users:register' %}">注册</a></div>{% endif %}
1.2说明:
1.2.1 user.is_authenticated :判断用户是否通过认证(是否登录)
- Django 的user 用户模型类自带属性
1.2.2 users:login的login 和 users:register的register是什么
- login 和 register 是子路由的名字

1.2.3 语法:
1.2.4 如果数据库中username发生改变,刷新后,此处的值也会发生改变
2、方案二:发送ajax请求获取用户信息
- 缺点:需要发送网络请求
<div class="login_btn fl">{# ajax渲染 #}
</div>
3、方案三: Vue读取cookie渲染用户信息
3.1 index.html 代码
- 利用vue进行if 判断: username是否存在
- 如果存在,显示 用户名和退出链接
- 如果不存在,显示登录 和 注册链接

3.2 index.js 代码

3.3 后端登录代码LoginView修改:需要写cookie

# 为了实现在首页显示用户名 需要将用户设置到cookie中response = redirect(reverse('contents:index'))# response.set_cookie('key', 'value', 'expiry') # 主键 , 值 , 过期时间 (秒)response.set_cookie('username', user.username, max_age=3600 * 24)# 响应结果 重定向到首页# return redirect(reverse('contents:index'))return response
3.4 后端注册代码RegisterView修改:需要写cookie (注册成功后进入首页时)

# 为了实现在首页显示用户名 需要将用户设置到cookie中response=redirect(reverse('contents:index'))# response.set_cookie('key', 'value', 'expiry') # 主键 , 值 , 过期时间 (秒)response.set_cookie('username', user.username, max_age=3600 * 24)# 响应结果# return http.HttpResponse('注册成功, 重定向到首页')# return redirect(reverse('contents:index'))return response
3.5 说明
3.5.1 index.js中getCookie()方法
- 定义在 common.js中

- 在index.html中分别引用了 common.js 和 index.js

3.5.2 后端向前端发送cookie
- Django 前端请求返回的要不是HttpResponse,要不是HttpResponseBase子类
- 通过response 设置cookie
4、刷新时,闪屏处理
- 首页刷新时,用户名显示的地方,会出现闪屏现象(界面值会发生改变),当网络不好时,会更加明显
- 解决办法:在某个标签中,添加 v-cloak

四、退出登录
1、django 的 logout()方法介绍
1.1 退出登录
- 回顾登录:
- 登录时,获取参数、校验参数、用户认证、状态保持 ,还有可能 设置了 cookie ,返回首页
- 退出登录:正好和登录相反(清理session会话信息)
1.2 logout()方法:
- Django用户认证系统提供了logout()方法
- 封装了清理session的操作,帮助我们快速实现登出一个用户
1.3 logout()位置:
• django.contrib.auth.init.py文件中
2、logout()方法使用
- 调用logout()方法
- 定义跳转的response
- 清除cookie中的 username
- 返回
class LogoutView(View):"""用户退出登录"""def get(self, request):"""实现用户退出登录的逻辑"""# 清除状态保持信息logout(request)# 重定向response = redirect(reverse('contents:index'))# 删除cookieresponse.delete_cookie('username')return response
3、修改子路由
# 退出登录path('logout/', views.LogoutView.as_view(), name='logout'),
4、前端index.html修改

五、判断用户是否登录
1、需求分析(应用场景分析):
- 当用户登录后,才能访问用户中心(或购物车等用户登录后才能访问的页面功能)。
- 如果用户未登录,就不允许访问用户中心,将用户引导到登录界面。
- 实现方案:
- 需要判断用户是否登录。
- 根据是否登录的结果,决定用户是否可以访问用户中心。
- 代码优化一:
- 实现方案实现的未登录跳转到登录界面,但这个代码虽然简单,但好多地方都需要用,
- 建议此代码独立出来,以便以后直接调用
- 代码优化二:
- 以上代码及优化化码都是基于功能实现的本身,没有考虑登录后,代码跳转到哪里
- 代码还需要优化,如果未登录,需要将现在需要进入的页面或功能,做为参数,传递到登录界面里面,登录成功后,再跳转到需要进入的页面和功能中。
2、is_authenticate 判断用户是否登录介绍:
• Django用户认证系统提供了方法request.user.is_authenticated()来判断用户是否登录。
• 如果通过登录验证则返回True。反之,返回False。
• 缺点:登录验证逻辑很多地方都需要,所以该代码需要重复编码好多次。
class UserInfoView(View):"""用户中心"""def get(self, request):"""提供个人信息界面"""if request.user.is_authenticated():return render(request, 'user_center_info.html')else:return redirect(reverse('users:login'))
3、全部优化功能实现方式一:login_required装饰器
- 此方法略,以后面一方法为主
4、全部优化功能实现方式二:继承LoginRequiredMixin类
from django.contrib.auth.mixins import LoginRequiredMixinclass UserInfoView(LoginRequiredMixin, View):"""用户个人中心"""# login_url = '/users/login/'def get(self, request):return render(request, 'user_center_info.html')
- LoginRequiredMixin类:实现了判断用户是否登录,直接进行登录界面判断
- 判断用户是否登录 指定未登陆用户的重定向地址:LOGIN_URL
- 缺省为Django.conf.global_settings.py中LOGIN_URL = ‘/accounts/login/’
- 如果不是需要在自己的配置文件中修改:LOGIN_URL = ‘/users/login/’
- 或者在类定义时,定义类属性login_url = '/users/login/'也可以
- 判断用户是否登录 指定未登陆用户的重定向地址:LOGIN_URL
- LoginRequiredMixin类:还自动传递当前页面的跳转路径
- 当登录成功后,需要跳转到该路径
- 所以需要在登录界面中提取该参数的值
- LoginRequiredMixin类:传递跳转跑径的参数是 next
- 该参数是AccessMixin类的属性 redirect_field_name = REDIRECT_FIELD_NAME
- REDIRECT_FIELD_NAME是django.contrib.auth.__init__中定义
- 如非必要,请不要修改此参数

class LoginView(View):"""用户名登录"""def get(self, request):"""提供登录页面:return: 登录页面"""return render(request, 'login.html')def post(self, 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 = login_form.cleaned_data.get('remembered')if not all([username, password]):return http.HttpResponseForbidden('缺少必传参数')# 认证登录用户# user = User.objects.get(username=username)# pwd = user.password # 密文# user.check_password()# if check_password(password, pwd):# print('密码正确')# else:# print('密码错误')user = authenticate(username=username, password=password)# print(user)if user is None:return render(request, 'login.html', {'account_errmsg': '账号或密码错误'})# 状态保持login(request, user)if remembered != True:# 没有记住登录 浏览器关闭就销毁request.session.set_expiry(0)else:# 记住登录 状态保持默认为2周request.session.set_expiry(None)next = request.GET.get('next')# print(next)if next:response = redirect(next)else:# 为了实现在首页显示用户名 需要将用户设置到cookie中response = redirect(reverse('contents:index'))# response.set_cookie('key', 'value', 'expiry') # 主键 , 值 , 过期时间 (秒)response.set_cookie('username', user.username, max_age=3600 * 24)# 响应结果 重定向到首页# return redirect(reverse('contents:index'))return responseelse:# print(login_form.errors)# print(login_form.errors.get_json_data())context = {'forms_errors': login_form.errors}return render(request, 'login.html', context=context)
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
