文章目录
用户登录功能实现
分析
- 业务处理流程
- 判断用户输入的账号是否为空
- 判断用户输入的密码是否为空
- 判断用户输入的账号与密码是否正确
- 请求方法:post
- url定义:
/users/login/
- 请求参数:url路径参数
参数 | 类型 | 前端是否必须传 | 描述 |
---|---|---|---|
user_account | 字符串 | 是 | 用户输入的账号可以是手机号也可以是用户名 |
password | 字符串 | 是 | 用户输入的密码 |
remember_me | 字符串 | 是 | 用户输入的“是否记住我” |
由于是后端请求,在向后端发起请求时,需要附带csrf_token
用户登出功能实现
分析
- 请求方法:get
- url定义:
/users/logout/
- 实现:调用django自带的logout(request)函数即可
superuser
- 创建管理员账号:
python manage.py createsuperuser
,然后填用户名,手机号,密码
前端代码
apps/users/views.py
from django.shortcuts import render,redirect,reverse
from django.views import View #导入类视图
from utils.json_fun import to_json_data
from utils.res_code import Code,error_map
from apps.users.forms import RegisterForm,LoginForm
from apps.users.models import Users
from django.contrib.auth import login,logout
import json
#用到渲染模板和提交数据用类视图比较多
class RegisterView(View):
"""
#一般会注释url
/users/register
"""
def get(self,request):
return render(request,'users/register.html')
#需要的字段:用户名,密码,确认密码,手机号,短信验证码
#请求方式post
#提交: form表单,ajax
#获取,验证
#步骤:
# 1.获取参数
# 2.校验参数
# 3.保存参数到数据库
# 4.返回给前端
#1.获取参数
def post(self,request):
json_data = request.body #从前端收到的json类型数据
if not json_data:
return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
dict_data = json.loads(json_data.decode('utf8')) #将json类型转成字典
#2.校验参数,用form表单方式校验
form = RegisterForm(data=dict_data)
if form.is_valid():
#3.保存数据到数据库
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
mobile = form.cleaned_data.get('mobile')
user = Users.objects.create_user(username=username,password=password,mobile=mobile)
login(request,user)
#4.返回给前端
return to_json_data(errmsg='恭喜您,注册成功')
else:
#定义一个错误信息列表
err_msg_list = []
for item in form.errors.get_json_data().values():
err_msg_list.append(item[0].get('message'))
err_msg_str = '/'.join(err_msg_list) #拼接错误信息为一个字符串
return to_json_data(errno=Code.PARAMERR,errmsg=err_msg_str)
#传给后台的参数:登录账号,密码,记住我
#请求方式:POST
#提交:ajax
class LoginView(View):
"""
/users/login/
"""
def get(self,request):
return render(request,'users/login.html')
def post(self,request):
#1.获取参数
json_data = request.body #获取的是bytes格式
if not json_data:
return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
dict_data = json.loads(json_data.decode('utf8')) #或者'utf-8' 将bytes格式解码成字符串,再转成json格式
#2.校验:账号,账号格式,是否为空,账号和密码,数据库
form = LoginForm(data=dict_data,request=request) #类的实例化
#3.返回给前端
if form.is_valid():
return to_json_data(errmsg='恭喜您!登陆成功!')
else:
#定义一个错误信息列表
err_msg_list = []
for item in form.errors.get_json_data().values():
err_msg_list.append(item[0].get('message'))
err_msg_str = '/'.join(err_msg_list) #拼接错误信息为一个字符串
return to_json_data(errno=Code.PARAMERR,errmsg=err_msg_str)
class LogoutView(View):
"""
/users/logout/
"""
def get(self,request):
logout(request) #删除redis数据库中的session 数据库和浏览器存在相同的session_id,证明这个账号处于登录状态
return redirect(reverse('users:login'))
base.html
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
{% block meta %}{% endblock %}
{% block title %}{% endblock %}
<link rel="stylesheet" href="{% static 'css/base/reset.css' %}">
<link rel="stylesheet" href="{% static 'css/base/common.css' %}">
{% block link %}{% endblock %}
<link rel="stylesheet" href="{% static 'css/base/side.css' %}">
<link rel="stylesheet" href="http://at.alicdn.com/t/font_684044_un7umbuwwfp.css">
</head>
<body>
<!-- header start -->
<header id="header">
<div class="mw1200 header-contain clearfix">
<!-- logo start -->
<h1 class="logo">
<a href="javascript:void(0);" class="logo-title">Python</a>
</h1>
<!-- logo end -->
<!-- nav start -->
<nav class="nav">
<ul class="menu">
<li><a href="{% url 'news:index' %}">首页</a></li>
<li><a href="{% url 'course:course' %}">在线课堂</a></li>
<li><a href="{% url 'doc:docDownload' %}">下载文档</a></li>
<li><a href="{% url 'news:search' %}">搜索</a></li>
</ul>
</nav>
<!-- nav end -->
<!-- login start -->
<div class="login-box">
{% if user.is_authenticated %}
<div class="author">
<i class="PyWhich py-user"></i>
<span>{{ user.username }}</span>
<ul class="author-menu">
{% if user.is_staff %}
<li><a href="javascript:void(0);">后台管理</a></li>
{% endif %}
<li><a href="{% url 'users:logout' %}">退出登录</a></li>
</ul>
</div>
{% else %}
<div>
<i class="PyWhich py-user"></i>
<span>
{# <a href="{% url 'users:login' %}" class="login">登录</a> / <a href="{% url 'users:register' %}"#}
<a href="{% url 'users:login' %}" class="login">登录</a> / <a href="{% url 'users:register' %}"
class="reg">注册</a>
</span>
</div>
{% endif %}
{# <div class="author hide">#}
{# <i class="PyWhich py-user"></i>#}
{# <span>qwertyui</span>#}
{# <ul class="author-menu">#}
{# <li><a href="javascript:void(0);">后台管理</a></li>#}
{# <li><a href="javascript:void(0);">退出登录</a></li>#}
{# </ul>#}
{# </div>#}
</div>
<!-- login end -->
</div>
</header>
<!-- header end -->
<!-- main start -->
{% block main %}
<main id="main">
<div class="w1200 clearfix">
<!-- main-contain start -->
{% block contain %}
{% endblock %}
<!-- main-contain end -->
<!-- side start -->
{% block side %}
<aside class="side">
<div class="side-activities">
<h3 class="activities-title">在线课堂<a href="javascript:void(0)">更多</a></h3>
<div class="activities-img">
<a href="javascript:void(0);" target="_blank">
<img src="{% static 'img/english.jpg' %}" alt="title">
</a>
<p class="activities-tips">对话国外小姐姐</p>
</div>
<ul class="activities-list">
<li>
<a href="javascript:void(0);" target="_blank">
<span class="active-status active-start">报名中</span>
<span class="active-title"><a
href="https://www.shiguangkey.com/course/2432"> Django 项目班</a></span>
</a>
</li>
<li>
<a href="javascript:void(0);" target="_blank">
<span class="active-status active-end">已结束</span>
<span class="active-title"><a
href="https://www.shiguangkey.com/course/2321">Python入门基础班</a></span>
</a>
</li>
</ul>
</div>
<div class="side-attention clearfix">
<h3 class="attention-title">关注我</h3>
<ul class="side-attention-address">
<li>
<a href="javascript:void(0);" target="_blank"><i class="PyWhich py-GitHub"></i>Taka</a>
</li>
<li>
<a href="javascript:void(0);" target="_blank"><i class="PyWhich py-zhihu"
style="color:rgb(0, 108, 226);"></i>Taka</a>
</li>
<li>
<a href="javascript:void(0);" target="_blank"><i class="PyWhich py-weibo"
style="color:rgb(245,92,110);"></i>Taka</a>
</li>
</ul>
<div class="side-attention-qr">
<p>扫码关注</p>
</div>
</div>
<div class="side-hot-recommend">
<h3 class="hot-recommend">热门推荐</h3>
<ul class="hot-news-list">
<li>
<a href="javascript:void(0)" class="hot-news-contain clearfix">
<div class="hot-news-thumbnail">
<img src="{% static 'img/python_web.jpg' %}"
alt="">
</div>
<div class="hot-news-content">
<p class="hot-news-title">Django调试工具django-debug-toolbar安装使用教程</p>
<div class="hot-news-other clearfix">
<span class="news-type">python框架</span>
<!-- 自带的 -->
<time class="news-pub-time">11月11日</time>
<span class="news-author">python</span>
</div>
</div>
</a>
</li>
</ul>
</div>
</aside>
{% endblock %}
<!-- side end -->
</div>
</main>
{% endblock %}
<!-- main end -->
<!-- footer start -->
<footer id="footer">
<div class="footer-box">
<div class="footer-content">
<p class="top-content">
<span class="link">
<a href="javascript:void(0)">关于Python</a> |
<a href="javascript:void(0)">我就是我</a> |
<a href="javascript:void(0)">人生苦短</a> |
<a href="javascript:void(0)">我用Python</a>
</span>
<span class="about-me">关于我: <i class="PyWhich py-wechat"></i> Taka</span>
</p>
<p class="bottom-content">
<span>地址: xxxx</span>
<span>联系方式: <a href="tel:400-1567-315">400-1567-315</a> (24小时在线)</span>
</p>
</div>
<p class="copyright-desc">
Copyright © 2008 - 2019 xxx有限公司. All Rights Reserved
</p>
</div>
</footer>
<!-- footer end -->
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
{% block domready %}{% endblock %}
</body>
</html>
- settings.py中设置session默认时间:3天
SESSION_COOKIE_AGE = 3 * 24 * 60 * 60
后端代码
apps/users/forms.py
#!/home/xiaoge/env python3.6
# -*- coding: utf-8 -*-
"""
__title__ = ' forms'
__author__ = 'xiaoge'
__mtime__ = '2019/5/29 上午10:37'
# code is far away from bugs with the god animal protecting
I love animals. They taste delicious.
┏┓ ┏┓
┏┛┻━━━━━━┛┻┓
┃ ☃ ┃
┃ ┳┛ ┗┳ ┃
┃ ┻ ┃
┗━┓ ┏━┛
┃ ┗━━━┓
┃ 神兽保佑 ┣┓
┃永无BUG!┏┛
┗┓┓┏━┳┓┏┛
┃┫┫ ┃┫┫
┗┻┛ ┗┻┛
"""
import re
from django import forms
from django_redis import get_redis_connection
from django.db.models import Q
from django.contrib.auth import login
from apps.verifications.constants import SMS_CODE_NUMS
from apps.users.models import Users
from apps.users import constants
class RegisterForm(forms.Form):
"""
#1.单个字段格式验证
# 2.手机号,格式,是否已注册
# 3.密码是否一致
# 4.短信验证码是否跟数据不一致
"""
#1.单个字段格式验证
username = forms.CharField(label='用户名',
max_length=20,
min_length=5,
error_messages={'min_length':'用户名长度要大于5',
'max_length':'用户名长度要小于20',
'required':'用户名不能为空'}
)
password = forms.CharField(label='密码',
max_length=20,
min_length=6,
error_messages={'min_length':'密码长度要大于6',
'max_length':'密码长度要小于20',
'required':'密码不能为空'}
)
password_repeat = forms.CharField(label='确认密码',
max_length=20,
min_length=6,
error_messages={'min_length':'密码长度要大于6',
'max_length':'密码长度要小于20',
'required':'确认密码不能为空'}
)
mobile = forms.CharField(label='手机号',
max_length=11,
min_length=11,
error_messages={'min_length':'手机号长度有误',
'max_length':'手机号长度有误',
'required':'手机号不能为空'}
)
sms_code = forms.CharField(label='短信验证码',
max_length=SMS_CODE_NUMS,
min_length=SMS_CODE_NUMS,
error_messages={'min_length':'短信验证码长度有误',
'max_length':'短信验证码长度有误',
'required':'短信验证码不能为空'}
)
def clean_mobile(self):
"""
# 2.手机号,格式,是否已注册
:return:
"""
tel = self.cleaned_data.get('mobile') #从前端获取
#验证输入的手机号格式
if not re.match(r'^1[3-9]\d{9}$',tel): #效果同verifications的forms.py中的RegexValidator
raise forms.ValidationError('手机号码格式不正确')
#验证输入的手机号在数据库中是否存在
if Users.objects.filter(mobile=tel).exists():
raise forms.ValidationError('手机号已注册!请重新输入!')
return tel
def clean(self):
"""
# 3.密码是否一致
# 4.短信验证码是否跟数据不一致
:return:
"""
#cleaned_data 就是读取前端form表单返回的值,返回类型为字典dict型
cleaned_data = super().clean() #继承clean的方法,重写
# 3.密码是否一致
password = cleaned_data.get('password') #从那个字典中获取键password对应的值
password_repeat = cleaned_data.get('password_repeat')
if password != password_repeat: #判断密码和确认密码是否一致
raise forms.ValidationError('两次输入密码不一致')
sms_text = cleaned_data.get('sms_code')
tel = cleaned_data.get('mobile')
# 4.短信验证码是否跟数据不一致
#连接redis数据库
redis_conn = get_redis_connection('verity_codes')
#构建短信验证码
sms_fmt = 'sms_{}'.format(tel)
#获取数据库中cleaned_data的短信验证码
real_sms = redis_conn.get(sms_fmt)
if (not real_sms) or (sms_text != real_sms.decode('utf-8')): #判断结果为总是抛出异常
raise forms.ValidationError('短信验证码错误')
class LoginForm(forms.Form):
"""
"""
user_account = forms.CharField() #这三个字段对应login.js中传入后端的字段
password = forms.CharField(label='密码',
max_length=20,
min_length=6,
error_messages={'min_length':'密码长度要大于6', #该参数是继承CharField,CharField又继承Field的error_messages
'max_length':'密码长度要小于20',
'required':'密码不能为空'}
)
remember_me = forms.BooleanField(required=False) #默认不记住
def __init__(self,*args,**kwargs):
# 字典方法,如果没有request这个键,返回None给self.request
#如果有request这个键,返回对应的值给self.request
self.request = kwargs.pop('request',None)
super().__init__(*args,**kwargs) #?
# super().__init__() #?
def clean_user_account(self):
"""
单字段验证
拼接字段clean_字段名
:return:
"""
user_info = self.cleaned_data.get('user_account') #cleaned_data是继承Form的方法,用于获取数据
if not user_info:
raise forms.ValidationError('用户账号不能为空!')
#这个验证不好,要改-----------------------------------------------------------------------------
if not re.match(r'^1[3-9]\d{9}$',user_info) and (len(user_info)<5 or len(user_info)>20):
raise forms.ValidationError('输入的账号格式不正确!')
return user_info
def clean(self):
cleaned_data = super().clean() #先继承,再重写
user_info = cleaned_data.get('user_account') #这三个用户输入的字段尽量不和前面的重名
passwd = cleaned_data.get('password') #该密码是从前端获得,是明文的
hold_login = cleaned_data.get('remember_me')
#1.数据库
#多条件的or连接,用到Q对象,django.db.models.Q
#要么用户名是账号,要么用户名是手机号
user_queryset = Users.objects.filter(Q(username=user_info) | Q(mobile=user_info))
if user_queryset:
user = user_queryset.first() #从queryset中拿到第一个对象,该情况下也只有一个对象
if user.check_password(passwd): #判断密码是否正确
#2.登录的操作,remember_me(记住session)
# 设置session过期时间
if not hold_login:
self.request.session.set_expiry(None)
else:
self.request.session.set_expiry(constants.USER_SESSION_EXPIRES)
#login(request,用户实例)
# request.session['username'] = username
#login()需要一个HttpRequest对象和一个 User对象,login()使用Django的会话框架将用户的ID保存在会话中
login(self.request,user) #login第一个参数是request,第二个参数是登录的用户的实例
else:
raise forms.ValidationError('密码不正确,请重新输入!')
else:
raise forms.ValidationError('用户账号不存在,请重新输入!')
别忘了配置login/logout路由
apps/users/constants.py
- 用于保存users下的文件中设置的常量
USER_SESSION_EXPIRES = 14 * 24 * 60 *60 #设置session保存时长为十四天