1.登录相关
要现实的功能:
- 用户名与密码登录
- 手机号和验证码登录
登录成功,将用户登录信息保存到session中,跳转home页面
登录失败,在页面提示报错信息
1.1 用户名与密码登录
效果图展示:

处理逻辑步骤:
- 页面展示(前端构建出登录的页面)(借用forms组件和不借助forms组件构建)
- 提交数据(采用post请求,把用户数据发给服务端)
- 去查询数据库之前,先用forms组件校验数据格式
- 校验成功:执行下一步
- 校验失败:在页面返回错误信息
- 根据数据库去校验登录信息
- 校验成功:将登录信息保存到session中,跳转页面
- 校验失败:页面展示错误信息
1.页面展示
构建逻辑:
用户访问某给url---》跳转到登陆页面(自己根据css和bootstrap构建好看的页面)---》登陆页面除了要有常规登录输入信息框之外,还要有一个链接,点击可以跳转到手机号验证码登录---》页面还要有一个下拉框,选择是客户登录还是管理员登录(因为我们客户和管理员是分表的)
会遇见的问题:
输入完登录信息,点击提交到django会报错,因为django内部有csrftoken,需要在form表单内添加一个csrf_token
不用forms组件构建的页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css"
integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<style>
body{
background-color: #f0ffff;
}
.box {
width: 20%;
border: 1px solid #ccc;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
margin: 10% auto;
padding: 15px;
}
.box a {
text-decoration: none;
float: right;
padding-top: 8px;
padding-right: 5px;
}
</style>
</head>
<body>
<div class="box">
<h3 style="text-align: center">登录</h3>
<form action="" method="post">
{% csrf_token %}
<div class="form-group">
<label for="name">用户名:</label>
<input type="text" id='name' placeholder="用户名" class="form-control" name="username">
</div>
<div class="form-group">
<label for="pwd">密码:</label>
<input type="password" id="pwd" placeholder="密码" class="form-control" name="password">
</div>
<div class="form-group">
<label for="role">角色:</label>
<select class="form-control" id="role">
<option value="customer">客户</option>
<option value="admin">管理员</option>
</select>
</div>
<input type="submit" value="登录" class="btn btn-primary">
<span>{{ error }}</span>
<a href="{% url 'sms_login' %}">短信登录</a>
</form>
</div>
</body>
</html>
上述,不用forms组件构建的页面有一大缺点,就是我们没有对用户传入的数据进行校验,而直接去查询数据库有没有这个用户了,
通常情况下,我们需要先对用户传的数据现校验一下,满足格式在去查数据库,因此就需要用到forms组件了(当然也可以自己在视图函数里写校验规则,但是它麻烦),django提供了一个forms组件可以很好的帮我们完成这个问题!
利用forms组件进行优化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css"
integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js"></script>
<style>
body {
background-color: #f0ffff;
}
.box {
width: 20%;
border: 1px solid #ccc;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
margin: 10% auto;
padding: 15px;
}
.box a {
text-decoration: none;
float: right;
padding-top: 8px;
padding-right: 5px;
}
.sub{
margin-top: 20px;
}
</style>
</head>
<body>
<div class="box">
<h3 style="text-align: center">登录</h3>
<form action="" method="post" novalidate id="myform">
{% csrf_token %}
{% for form in form_obj %}
<div class="form-group">
<label for="{{ form.id_for_label }}">{{ form.label }}:</label>
{{ form }}
<span style="color: red;float:right" id="error-msg">{{ form.errors.0 }}</span>
</div>
{% endfor %}
<div class="sub">
<input type="submit" value="登录" class="btn btn-primary">
<span style="color: red" id="error">{{ error }}</span>
<a href="{% url 'login_sms' %}">短信登录</a>
</div>
</form>
</div>
</body>
<script>
$(function () {
// 点击哪个input框就消除下面的错误信息
$('#myform').on('click', 'input', function () {
$(this).next().text('')
$('#error').text('')
})
})
</script>
</html>
用到的forms组件
class LoginPwdForm(forms.Form):
role = forms.ChoiceField(
choices=((1, "客户"), (2, "管理员")),
label="角色",
initial=1,
widget=forms.Select(attrs={'class': 'form-control'})
)
username = forms.CharField(max_length=32, label='用户名', required=True,
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '用户名'}),
error_messages={
'max_length': '用户名最大32位',
'required': '用户名不能为空'
})
password = forms.CharField(max_length=16, label='密码', required=True,
widget=forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': '密码'}),
error_messages={
'max_length': '密码最大16位',
'required': '密码不能为空'
})
def clean_password(self):
# 对于校验通过的密码进行md5加密之后在返回
return md5(self.cleaned_data['password'])
知识点补充:
2.提交数据
构建逻辑:
form表单点击登录,将用户输入内容提交给指定url,在后端可以根据request.POST.get获取到用户输入信息
3.校验登录数据
构建逻辑:
在视图函数里面去根据传来的登录信息,查找数据库有没有---》没有,在给登录页面返回错误信息,并在页面展示---》有,则跳转到其他页面显示登录成功并写入session
注意点:
1.我们密码在数据库里一般是加密形式,不是明文形式。因此,我们需要对用户登录输入的密码先加密(md5加密和加盐,一般可以在forms组件里就进行加密即可,不需要在视图函数里进行),再去查找数据库有没有
2.session借助cookie发送给浏览器,我们在django服务端存一份,session可以存放在文件、数据库、缓存中,我们这边先采用存在缓存里
不用forms组件后端校验逻辑
from django.shortcuts import render,redirect
from web import models
from utils.encrypt import md5
def pwd_login(request):
if request.method == 'GET':
return render(request,'pwd_login.html')
# 当时post请求时,获取用户提交数据,查找数据库校验
username = request.POST.get('username')
password = request.POST.get('password')
# 因为数据库里的密码是md5加密加盐的,我们这里也需要将其加密加盐一下在查找
password = md5(password)
# 查找数据库有没有这个用户,注意要添加active查找活跃用户,而不是被删除的用户
use_obj = models.Administrator.objects.filter(active='1',username=username,password=password)
# 查不到用户返回错误信息
if not use_obj:
return render(request,'pwd_login.html',{'error':'用户名或密码错误'})
return redirect('/home/')
用forms组件后端校验逻辑
# 密码登录视图函数
def login_pwd(request):
if request.method == 'GET':
form_obj = myforms.LoginPwdForm()
return render(request, 'login_pwd.html', locals())
if request.method == 'POST':
form_obj = myforms.LoginPwdForm(request.POST)
role_mapping = {
'1': 'CUSTOMER',
'2': 'ADMIN'
}
if not form_obj.is_valid():
return render(request, 'login_pwd.html', locals())
data_dict = form_obj.cleaned_data
role = data_dict.pop('role')
if role_mapping[role] == 'ADMIN':
# 查找数据库有没有这个用户,注意要添加active查找活跃用户,而不是被删除的用户
use_obj = models.Administrator.objects.filter(active=1).filter(**data_dict).first()
else:
use_obj = models.Customer.objects.filter(active=1).filter(**data_dict).first()
# 查不到用户返回错误信息
if not use_obj:
error = '用户名或密码错误'
return render(request, 'login_pwd.html', locals())
# 用户名和密码正确,保存到session然后跳转
request.session['user_info'] = {'role': role_mapping[role], 'username': use_obj.username, 'id': use_obj.id}
return redirect(settings.LOGIN_HOME)
知识点补充:
1.2 手机验证码登录
效果图:

处理逻辑步骤:
- 页面展示(前端根据forms组件构建出登录的页面)
- 获取验证码(采用ajax请求提交,把用户角色和手机号发给服务端校验)
- 根据角色,去对应数据库查询该手机号格式是否正确,是否注册,未注册先提示注册再登录
- 已注册,并且格式正确,再生成验证码,调用第三方接口发送验证码
- 验证码发送成功,则修改前端页面获取验证码为倒计时,并且把验证码保存到redis中
- 验证码发送失败,展示错误信息
- 点击登录按钮,提交验证码、手机号和角色提交数据到服务端(ajax提交)
- 校验成功:将登录信息保存到session中,跳转页面
- 校验失败:页面展示错误信息
1.页面展示
forms组件构建的验证码登录页面
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css"
integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js"></script>
<script src="{% static 'js/csrf.js' %}"></script>
<style>
body {
background-color: #f0ffff;
}
.box {
width: 20%;
border: 1px solid #ccc;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
margin: 10% auto;
padding: 15px;
}
.box a {
text-decoration: none;
float: right;
padding-top: 8px;
padding-right: 5px;
}
.col-md-4 {
margin-left: -15px;
margin-right: -15px;
}
.sub {
margin-top: 20px;
}
</style>
</head>
<body>
<div class="box">
<h3 style="text-align: center">登录</h3>
<form action="" method="post" id="smsForm">
{# {% csrf_token %}#}
{% for form in form_obj %}
{% if form.name == 'code' %}
<div class="form-group">
<label for="{{ form.id_for_label }}">{{ form.label }}:</label>
<div class="row">
<div class="col-md-8">{{ form }}
</div>
<div class="col-md-4">
<input type="button" value="获取验证码" class="form-control sms-btn">
</div>
</div>
</div>
{% else %}
<div class="form-group">
<label for="{{ form.id_for_label }}">{{ form.label }}:</label>
{{ form }}
<span style="color: red;float: right" class="error-message">{{ form.errors.0 }}</span>
</div>
{% endif %}
{% endfor %}
<div class="sub">
<button class="btn btn-primary login-btn" type="button">登录</button>
<span style="color: red" class="error-message" id="code-error">{{ form.errors.0 }}</span>
<a href="{% url 'login_pwd' %}">密码登录</a>
</div>
</form>
</div>
</body>
</html>
2.点击获取验证码
前端js代码
$(function () {
// 页面加载完毕执行下述函数
// 1.点击获取验证码事件
bingSendSmsEvent();
// 2.点击登录按钮,提交验证码登录信息
bingSubmitSmsEvent()
})
var $btn = $('.sms-btn');
function bingSendSmsEvent() {
$btn.click(function () {
// 向后端提交手机号,获取验证码
$.ajax({
type: 'POST',
url: '{% url "tencent" %}',
data: {
'mobile': $('#id_mobile').val(),
'role': $('#id_role').val(),
},
dataType: 'JSON',
success: function (res) {
// 如果调用腾讯接口发送短信成功,则修改页面获取验证码为倒计时
if (res.status) {
changeSendBtn()
} else {
$.each(res.detail, function (k, v) {
$('#id_' + k).next().text(v[0])
})
}
}
})
})
}
// 点击哪个input框就消除下面的错误信息
$('#smsForm').on('click', 'input', function () {
// 清楚所有的错误
$(".error-message").empty();
})
// 修改获取验证码按钮
function changeSendBtn() {
var $time = 60;
$btn.prop('disabled', true)
var ID = setInterval(function () {
$btn.val($time + ' 秒重试')
$time--
if ($time < 1) {
clearInterval(ID);
$btn.val('获取验证码');
$btn.prop('disabled', false);
}
}, 1000)
}
后端视图代码
def tencent_sms(request):
res = myresponse.BasicResponse()
# 校验手机号和角色
form_obj = myforms.MobileForm(request.POST)
# 校验手机号、角色、动态生成一个验证码
# 调用腾讯发短信接口:传入手机号和验证码
# 上述这些都在forms组件里做了
if not form_obj.is_valid():
res.detail = form_obj.errors
return JsonResponse(res.dict, json_dumps_params={'ensure_ascii': False})
# 校验通过并发送验证码成功之后
res.status = True
return JsonResponse(res.dict, json_dumps_params={'ensure_ascii': False})
forms组件校验代码
class MobileForm(forms.Form):
role = forms.ChoiceField(
choices=((1, "客户"), (2, "管理员")),
label="角色",
initial=1,
widget=forms.widgets.Select(attrs={'class': 'form-control'})
)
mobile = forms.CharField(max_length=11, label='手机号', required=True,
widget=forms.widgets.TextInput(attrs={'class': 'form-control', 'placeholder': '手机号'}),
validators=[
RegexValidator(r'^1[358]\d{9}$', '手机号格式不正确')
],
error_messages={
'max_length': '手机号最大11位',
'required': '手机号不能为空'
})
def clean_mobile(self):
role = self.cleaned_data.get('role')
mobile = self.cleaned_data.get('mobile')
if not role:
return mobile
if role == '1':
is_exist = models.Customer.objects.filter(active=1,mobile=mobile).exists()
else:
is_exist = models.Administrator.objects.filter(active=1,mobile=mobile).exists()
if not is_exist:
raise ValidationError('该手机号还未注册,请先注册')
# 手机号存在,调用腾讯接口发送短信
# 先生成验证码
code = str(random.randint(1000, 9999))
print(code)
# 调用接口发短信
# is_success = tencent.send_sms(mobile, code)
is_success = True
if not is_success:
raise ValidationError("短信发送失败,请重试")
# 短信发送成功,将信息保存到redis中
conn = get_redis_connection('default')
conn.set(mobile,code,ex=60)
return mobile
补充知识点: