1.管理员案例
1.1管理员数据库
1.1.1 表结构
1.1.2 管理员表的建立
class Admin(models.Model):
"""管理员表"""
username = models.CharField(max_length=32, verbose_name="用户名")
password = models.CharField(max_length=64, verbose_name="密码")
1.2 管理员数据的管理
1.2.1 管理员列表的展示
admin_list.html
{% extends 'layout.html' %}
{% block content %}
<div style="margin-bottom: 10px">
<a type="button" class="btn btn-success" href="/admin/add"><span class="glyphicon glyphicon-plus-sign"
aria-hidden="true"></span> 添加管理员</a>
<div style="width: 300px; float: right">
<form method="get" action="/admin/list">
<div class="input-group">
<input type="text" class="form-control" name="username" placeholder="Search for..."
value="{{ username }}">
<span class="input-group-btn">
<button class="btn btn-default" type="submit"><span class="glyphicon glyphicon-search"
aria-hidden="true"></span></button>
</span>
</div>
</form>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">管理员列表</div>
<div class="bs-example" data-example-id="hoverable-table">
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>密码</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for obj in queryset %}
<tr>
<td>{{ obj.id }}</td>
<td>{{ obj.username }}</td>
<td>******</td>
<td>
<a class="btn btn-success" href="/admin/{{ obj.id }}/reset_password">重置密码</a>
<a class="btn btn-warning btn-sm" href="/admin/{{ obj.id }}/update">编辑</a>
<a class="btn btn-danger btn-sm" href="/admin/{{ obj.id }}/del">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<ul class="pagination">
{{ page_str }}
</ul>
</div>
{% endblock %}
admin.py
def admin_list(request):
"""管理员列表"""
# 搜索
data_dict = {}
username = request.GET.get('username', "")
if username:
# {关键字: 范围, }
data_dict['username__contains'] = username
# data_dict = {'username__contains': 'Y'}
queryset = models.Admin.objects.filter(**data_dict)
# 分页功能
page_object = Pagination(request, queryset)
context = {
"queryset": page_object.query_set,
"page_str": page_object.createHtml()
}
return render(request, "admin_list.html", context)
- 搜索功能
{关键字:范围} 利用字典作为搜索的条件集合
# 搜索 data_dict = {} username = request.GET.get('username', "") if username: # {关键字: 范围, } data_dict['username__contains'] = username # data_dict = {'username__contains': 'Y'} queryset = models.Admin.objects.filter(**data_dict)
- 分页功能
page_object = Pagination(request, queryset) context = { "queryset": page_object.query_set, "page_str": page_object.createHtml() }
1.2.2 管理员的添加
admin.py
def admin_add(request):
if request.method == "GET":
form = adminModelForm()
return render(request, "admin_add.html", {"form": form})
else:
form = adminModelForm(request.POST)
if form.is_valid():
form.save()
return redirect("/admin/list")
else:
return render(request, "admin_add.html", {"form": form})
form = adminModelForm()
在adminModelForm()
中创建表单的样式并且规定一些校验规则
- 表单样式创建
class adminModelForm(BootStrapModelForm):
# 新增一个确认密码字段
confirm_password = forms.CharField(
max_length=64,
label='确认密码',
widget=forms.PasswordInput # 密码格式
# 加上后,若校验失败,密码不会清空
# widget=forms.PasswordInput(render_value=True)
)
class Meta:
model = models.Admin
fields = ['username', 'password', 'confirm_password']
# 额外给password增加密码输入框的样式
widgets = {
'password': forms.PasswordInput
# 加上后,若校验失败,密码不会清空
# widget=forms.PasswordInput(render_value=True)
}
class BootStrapModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for key, value in self.fields.items():
value.widget.attrs = {'class': 'form-control', "placeholder": value.label}
- 新建的管理员名称不允许重复(钩子函数实现)
def clean_username(self):
username = self.cleaned_data.get('username')
# 判断管理员是否存在
flag = models.Admin.objects.filter(username=username).exists()
if flag:
raise ValidationError("该管理员已存在")
return username
-
确认两次输入密码一致
- md5的应用
encrypt.py
def md5(data_string): # 导入django自带的sault obj = hashlib.md5(settings.SECRET_KEY.encode('utf-8')) obj.update(data_string.encode('utf-8')) return obj.hexdigest()
- 钩子函数实现判断规则
# 钩子函数对密码进行md5加密 def clean_password(self): password = self.cleaned_data.get('password') return md5(password) # md5加密并password返回该值 # 钩子函数校验确认密码 def clean_confirm_password(self): # txt_password 表示用户输入的md5加密完的密码 # txt_confirm_password 表示用户输入的确认密码 txt_password = self.cleaned_data.get('password') txt_confirm_password = self.cleaned_data.get('confirm_password') # md5_confirm_password 表示用户输入的md5加密后的确认密码 md5_confirm_password = md5(txt_confirm_password) if txt_password == md5_confirm_password: # 校验通过(密码一致),返回输入的数据 # 对于此案例,不保存confirm_password字段,因此用处不大 return md5_confirm_password else: raise ValidationError("密码不一致")
forms.py
class adminModelForm(BootStrapModelForm):
# 新增一个确认密码字段
confirm_password = forms.CharField(
max_length=64,
label='确认密码',
widget=forms.PasswordInput # 密码格式
# 加上后,若校验失败,密码不会清空
# widget=forms.PasswordInput(render_value=True)
)
class Meta:
model = models.Admin
fields = ['username', 'password', 'confirm_password']
# 额外给password增加密码输入框的样式
widgets = {
'password': forms.PasswordInput
# 加上后,若校验失败,密码不会清空
# widget=forms.PasswordInput(render_value=True)
}
def clean_username(self):
username = self.cleaned_data.get('username')
# 判断管理员是否存在
flag = models.Admin.objects.filter(username=username).exists()
if flag:
raise ValidationError("该管理员已存在")
return username
# 钩子函数对密码进行md5加密
def clean_password(self):
password = self.cleaned_data.get('password')
return md5(password) # md5加密并password返回该值
# 钩子函数校验确认密码
def clean_confirm_password(self):
# txt_password 表示用户输入的md5加密完的密码
# txt_confirm_password 表示用户输入的确认密码
txt_password = self.cleaned_data.get('password')
txt_confirm_password = self.cleaned_data.get('confirm_password')
# md5_confirm_password 表示用户输入的md5加密后的确认密码
md5_confirm_password = md5(txt_confirm_password)
if txt_password == md5_confirm_password:
# 校验通过(密码一致),返回输入的数据
# 对于此案例,不保存confirm_password字段,因此用处不大
return md5_confirm_password
else:
raise ValidationError("密码不一致")
1.2.3管理员的删除
def admin_del(request, nid):
models.Admin.objects.filter(id=nid).delete()
return redirect("/admin/list")
1.2.4 管理员的更新
views.py
def admin_update(request, nid):
row_object = models.Admin.objects.filter(id=nid).first()
# 先判断删除的id数据库是否存在
if not row_object:
return redirect("/admin/list")
title = "管理员编辑"
if request.method == "GET":
# instance=row_object将查询到待修改的数据填充到表单
form = admin_editModelForm(instance=row_object)
return render(request, "change.html", {"form": form, "title": title})
# instance=row_object 更新后,覆盖原有数据
form = admin_editModelForm(data=request.POST, instance=row_object)
if form.is_valid():
form.save()
return redirect("/admin/list")
return render(request, "change.html", {"form": form, "title": title})
1.2.5 管理员密码的重置
views.py
def admin_reset_password(request, nid):
row_object = models.Admin.objects.filter(id=nid).first()
title = "重置密码--{}".format(row_object.username)
if not row_object:
return redirect("/admin/list")
if request.method == "GET":
form = admin_reset_password_ModelForm()
return render(request, "change.html", {"form": form, "title": title})
form = admin_reset_password_ModelForm(data=request.POST, instance=row_object)
if form.is_valid():
form.save()
return redirect("/admin/list")
return render(request, "change.html", {"form": form, "title": title})
校验规则(钩子函数)
在admin_reset_password_ModelForm
中实现
- 重置的密码不能与原来密码一致
def clean_password(self):
password = self.cleaned_data.get('password')
MD5_pwd = md5(password)
# form = admin_reset_password_ModelForm(data=request.POST, instance=row_object)
# 此句话为调用admin_reset_password_ModelForm返回的表单
# 利用 self.instance.pk 可以获取到row_object该行数据的id
# 判断该id对应的密码是否为输入的密码(比较的是加密完的密码)
result = models.Admin.objects.filter(id=self.instance.pk,password=MD5_pwd).exists()
if result:
raise ValidationError("不能与原密码一致!")
return MD5_pwd # md5加密并password返回该值
- 两次输入的密码应一致
def clean_confirm_password(self):
# txt_password 表示用户输入的md5加密完的密码
# txt_confirm_password 表示用户输入的确认密码
txt_password = self.cleaned_data.get('password')
txt_confirm_password = self.cleaned_data.get('confirm_password')
# md5_confirm_password 表示用户输入的md5加密后的确认密码
md5_confirm_password = md5(txt_confirm_password)
if txt_password == md5_confirm_password:
# 校验通过(密码一致),返回输入的数据
# 对于此案例,不保存confirm_password字段,因此用处不大
return md5_confirm_password
else:
raise ValidationError("密码不一致")
1.3 更新页面的整合
各个部分的更新页面都差不多,因此对页面进行整合为change.html
change.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
<link rel="stylesheet" href="{% static 'plugins/bootstrap-datepicker/css/bootstrap-datepicker.css' %}">
<style>
.navbar {
border-radius: 0;
}
</style>
{% block css %}{% endblock %}
</head>
<body>
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">管理系统</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="/admin/list">管理员管理</a></li>
<li><a href="/depart/list">部门管理</a></li>
<li><a href="/user/list">员工管理</a></li>
<li><a href="/number/list">靓号管理</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">登录</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">admin<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">个人中心</a></li>
<li><a href="#">设置</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">注销</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">{{ title }}</div>
<div class="panel-body">
<div class="bs-example" data-example-id="simple-form-inline">
<form class="form" method="post" novalidate>
{% csrf_token %}
{#创建表单,form为userinfo各字段的表单#}
{#每一个field都是一个字段的输入框#}
{% for field in form %}
<div class="form-group">
<label>{{ field.label }}</label>
{{ field }}
<span style="color: red">{{ field.errors.0 }}</span>
{# field.errors.0显示第一条错误即可 #}
</div>
{% endfor %}
<input type="submit" class="btn btn-success" value="提交">
</form>
</div>
</div>
</div>
</div>
<script src="{% static 'js/jquery-3.7.1.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-datepicker/js/bootstrap-datepicker.js' %}"></script>
<script src="{% static 'plugins/bootstrap-datepicker/locales/bootstrap-datepicker.zh-CN.min.js' %}"></script>
<script>
$(function () {
$('#dt_creat_time').datepicker({
format: 'yyyy-mm-dd',
//startDate: '0',//最早日期为当前日期,无法wangqian
language: "zh-CN",
autoclose: true
});
})
</script>
{% block js %}{% endblock %}
</body>
</html>
{% block content %}
{% endblock %}
固定参数
- 表单标题
<div class="panel-heading">{{ title }}</div>
从每个操作的视图函数中传进来
- 表单
form为view函数创建好的表单标签(包含input输入框、样式、value值)<div class="panel-body"> <div class="bs-example" data-example-id="simple-form-inline"> <form class="form" method="post" novalidate> {% csrf_token %} {#创建表单,form为userinfo各字段的表单#} {#每一个field都是一个字段的输入框#} {% for field in form %} <div class="form-group"> <label>{{ field.label }}</label> {{ field }} <span style="color: red">{{ field.errors.0 }}</span> {# field.errors.0显示第一条错误即可 #} </div> {% endfor %} <input type="submit" class="btn btn-success" value="提交"> </form> </div> </div>
form标签中的action
对于form中的action关键字,指定了表单传数据的目的地,若不写默认form变量的来源位置
使用默认位置,不仅简便,而且便于整合,不用额外在action中拼接nid
2. 登录页面
2.1登陆功能的实现
login.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
<link rel="stylesheet" href="{% static 'plugins/bootstrap-datepicker/css/bootstrap-datepicker.css' %}">
<style>
.login {
position: fixed;
width: 400px;
height: 300px;
border: 1px solid #adadad;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
padding: 20px 10px;
box-shadow: 2px 2px 5px #8c8c8c;
}
.font {
text-align: center;
}
</style>
</head>
<body>
<div class="login">
<form method="post">
{% csrf_token %}
<div class="font"><h1>用户登录</h1></div>
<div class="form-group">
{% for obj in form %}
<label>{{ obj.label }}</label>
{{ obj }}
<span style="color: red">{{ obj.errors.0 }}</span>
{% endfor %}
</div>
<button type="submit" class="btn btn-success">登录</button>
</form>
</div>
</body>
</html>
login.py
def login(request):
if request.method == 'GET':
form = login_ModelForm()
return render(request, 'login.html', {'form': form})
form = login_ModelForm(request.POST)
# 将输入的用户名密码与数据库的用户名密码进行比对
if form.is_valid():
dic = form.cleaned_data
# 判断用户名密码是否正确(是否查到)
admin_object = models.Admin.objects.filter(**dic).first()
if not admin_object:
# 在form中添加错误(错误地方,错误) '
# 将错误信息展示到密码上
form.add_error("password", "用户名或密码错误")
return render(request, 'login.html', {'form': form})
# 验证成功——开始cookie验证
# request.session['info'] = admin_object.username
# 以下这句话,完成了以下功能
"""
1.生成cookie存储到浏览器中
2.将cookie和浏览器的信息存储到session中(django存到数据库中)
session主要存储 key(cookie) data(根据下面数据形成的) 日期等等
"""
request.session['info'] = {'id': admin_object.id, 'username': admin_object.username}
return redirect('/admin/list')
return render(request, 'login.html', {'form': form})
login_ModelForm()
表单类
BootStrapModelForm
对表单的样式进行更改
实现了表单的建立和对输入密码进入md5加密,便于与数据库比较class login_ModelForm(BootStrapModelForm): class Meta: model = models.Admin fields = ['username', 'password'] # 将输入的密码进行md5,加密 def clean_password(self): password = self.cleaned_data.get('password') MD5_pwd = md5(password) return MD5_pwd
获取表单数据的方法
form.cleaned_data.get(xxx)
该方法能获取对应name的数据,返回一个字符串
username = form.cleaned_data.get('username') password = form.cleaned_data.get('password') res = models.Admin.objects.filter(username=username,password=password).first()
form.cleaned_data
该方法返回表单中所有的输入数据,将name与数据以字典的形式对应返回
# 返回值的字典形式可以直接传入查询数据库的条件 dic = form.cleaned_data # 判断用户名密码是否正确(是否查到) admin_object = models.Admin.objects.filter(**dic).first()
错误信息的提示
对于用户名与密码不匹配的情况,要显示错误提示并重新登录
- 判断过程中需要获取输入的数据以及识别失败返回登陆页面,因此需要在视图函数中判断,而不能在钩子函数实现(不匹配时需返回登陆页面)
- 判断表单是否返回数据(表单是否为空)
admin_object = models.Admin.objects.filter(**dic).first() if not admin_object: form.add_error("password", "用户名或密码错误") return render(request, 'login.html', {'form': form}) request.session['info']={'id':admin_object.id,'username': admin_object.username}
form.add_error("password", "用户名或密码错误")
参数1表示错误信息显示的位置 参数2表示显示的错误信息 该语句新增错误信息到form的errors中,便于提示错误信息使用位置
login.html
<div class="login"> <form method="post"> {% csrf_token %} <div class="font"><h1>用户登录</h1></div> <div class="form-group"> {% for obj in form %} <label>{{ obj.label }}</label> {{ obj }} <span style="color: red">{{ obj.errors.0 }}</span> {% endfor %} </div> <button type="submit" class="btn btn-success">登录</button> </form> </div>
2.2 cookie与session
2.2.1 基本介绍
- 短连接
http://127.0.0.1:8000/admin/list/
https://127.0.0.1:8000/admin/list/
对于http来说,建立无状态短连接(浏览器发送请求,后端网页接收请求,并响应请求,完成这个过程后断开连接)
实现浏览器与后端网页长久通信
当浏览器向后端网页发送请求,后端网页向浏览器发送一个响应的过程中,后端网页给该浏览器生成一个为一个标识码**(字典的形式)来代表该浏览器,该标识码存储在该浏览器内,这个标识码就叫做cookie**;在后端网页给浏览器生成发送标识码时,也将该标识码与对应的浏览器存储在后端网页的session中;在之后的连接中,浏览器直接向后端网页发送cookie标识,在后端网页的session中查询是否存在,从而确定是否连接。
- cookie:随机字符串(用来标识)
- session:后端网页存储用户信息的部分,主要形式是数据库、redis、文件(django主要是数据库形式)
def login(request):
if request.method == 'GET':
form = login_ModelForm()
return render(request, 'login.html', {'form': form})
form = login_ModelForm(request.POST)
if form.is_valid():件
dic = form.cleaned_data
# 判断用户名密码是否正确(是否查到)
admin_object = models.Admin.objects.filter(**dic).first()
if not admin_object:
form.add_error("password", "用户名或密码错误")
return render(request, 'login.html', {'form': form})
request.session['info'] = {'id': admin_object.id, 'username': admin_object.username}
return redirect('/admin/list')
return render(request, 'login.html', {'form': form})
当用户名与密码匹配成功后,实现浏览器与后端网页长久通信(验证cookie)
request.session['info']={'id': admin_object.id, 'username': admin_object.username}
以下这句话,完成了以下功能:
将后端网页生成cookie存储到浏览器中
将cookie和浏览器的信息存储到session中(django存到数据库中)、
session主要存储 key(cookie) data(根据下面数据形成的) 日期等等
生成的cookie存储到session的cookie部分 信息info 存储对session对应的data部分
2.2.2基本应用
# 验证成功,生成cookie和session,从session获取
# request.session['info'] = {'id': admin_object.id, 'username': admin_object.username}
info_dic = request.session.get('info')
# 若未登录,则info_dic为空 若登录,则info_dic为传入的数据
if not info_dic:
return redirect('/login/')
在每一个视图函数中都加入该语句,直接视图函数时,判断是否有cookie(是否登录)
- 若已登录,则对应浏览器生成cookie并且存取相应的数据信息到session中,根据获取某个浏览器在session中的数据来判断是否处于登录状态
- 若未登录,直接跳转到登录页,不再继续执行视图函数
缺点
每一个视图函数均需要增添这些语句,非常冗余
2.2.3 中间件
2.2.3.1 中间件的原理
- 中间件=django中的类
书写中间件(类的位置):
app01-middleware-auth.py
引用的Django类:
from django.utils.deprecation import MiddlewareMixin
中间件的注册:
setting.py 中的MIDDLEWARE添加'app01.middleware.auth.M1'
-
浏览器向django发送请求时,需要依次经过多个中间件后,视图函数才可以接收请求.请求顺序:浏览器-M1-M2-M3-视图函数
-
视图函数响应请求时,也是依次经过中间件后,浏览器才可以接收到响应。响应顺序:视图函数-M3-M2-M1-浏览器
auth.py
class M1(MiddlewareMixin): def process_request(self, request): print("m1-process_request") return # return HttpResponse("无权访问") def process_response(self, request, response): print("m1-process_response") return response class M2(MiddlewareMixin): def process_request(self, request): print("m2-process_request") return def process_response(self, request, response): print("m2-process_response") return response class M3(MiddlewareMixin): def process_request(self, request): print("m3-process_request") return def process_response(self, request, response): print("m3-process_response") return response
请求与响应顺序:
m1-process_request m2-process_request m3-process_request m3-process_response m2-process_response m1-process_response
-
若在依次请求中间件时,某个中间件直接返回响应,则其他中间件不在执行,直接响应浏览器
用于实现登录校验,符合无返回值,不符合有返回值
- 如果方法中没有返回值(返回None),继续向后走
- 如果有返回值 HttpResponse、render 、redirect,则不再继续向后执行
class M1(MiddlewareMixin): def process_request(self, request): print("m1-process_request") return HttpResponse("无权访问") def process_response(self, request, response): print("m1-process_response") return response class M2(MiddlewareMixin): def process_request(self, request): print("m2-process_request") return def process_response(self, request, response): print("m2-process_response") return response class M3(MiddlewareMixin): def process_request(self, request): print("m3-process_request") return def process_response(self, request, response): print("m3-process_response") return response
m1-process_request m1-process_response 中间件M1直接return HttpResponse("无权访问") 则不再执行其他中间,直接响应浏览器
2.2.3.2 中间件的登录校验
- 中间件的设计
模板:
class AuthMiddleware(MiddlewareMixin):
def process_request(self, request):
...
...
...
return
auth.py
class AuthMiddleware(MiddlewareMixin):
def process_request(self, request):
# 1.中间件只能检验出登陆视图函数的其他函数
# request.path_info 获取当前用户请求的url 判断是否为登录视图的url
# 如果是,不进行任何检验,直接进入登陆页面
print(request.path_info)
if request.path_info == '/login/':
info_dic = request.session.get('info')
if info_dic:
return
return redirect('/login')
- 中间件只能检验出登陆视图函数的其他函数
- request.path_info 获取当前用户请求的url 判断是否为登录视图的url
如果是登陆页面,不进行任何检验,直接进入登陆页面- 如果不是登录页面,则需要判断是否有登录信息
- 获取生成cookie时存的数据
- 获取生成cookie时存的数据
- 校验失败,直接返回,不允许进入视图函数
2.3 用户注销功能
删除存储的session信息,并返回登录页面即可
login.py
def logout(request):
# 注销就是清除clear
request.session.clear()
return redirect('/login')
layout.html
...
...
...
<body>
<nav class="navbar navbar-default">
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="/admin/list">管理员管理</a></li>
<li><a href="/depart/list">部门管理</a></li>
<li><a href="/user/list">员工管理</a></li>
<li><a href="/number/list">靓号管理</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">{{ request.session.info.username }}<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">个人中心</a></li>
<li><a href="#">设置</a></li>
<li role="separator" class="divider"></li>
<li><a href="/logout">注销</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
...
...
...
{{ request.session.info.username }}
用户的登录信息存储在session中来源:login视图函数中
request.session['info'] = {'id': admin_object.id, 'username': admin_object.username}
2.4 图片验证码功能
2.4.1 python生成图片
import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter
def check_code(width=120, height=30, char_length=5, font_file='Monaco.ttf', font_size=28):
code = []
# 创建一个图片
img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
# 创建一个画笔
draw = ImageDraw.Draw(img, mode='RGB')
# 生成随机随机字母
def rndChar():
# ASCII码对应字母
return chr(random.randint(65, 90))
# 生成随机颜色
def rndColor():
return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255))
# 将文字写入图片中
font = ImageFont.truetype(font_file, font_size)
for i in range(char_length):
char = rndChar()
code.append(char)
h = random.randint(0, 4)
draw.text([i * width / char_length, h], char, font=font, fill=rndColor())
# 写干扰点
for i in range(40):
draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
# 写干扰圆圈
for i in range(40):
draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
x = random.randint(0, width)
y = random.randint(0, height)
draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor())
# 画干扰线
for i in range(5):
x1 = random.randint(0, width)
y1 = random.randint(0, height)
x2 = random.randint(0, width)
y2 = random.randint(0, height)
draw.line((x1, y1, x2, y2), fill=rndColor())
img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
return img, ''.join(code)
if __name__ == '__main__':
# check_code()返回一张图片以及生成的随机字母
img, code_str = check_code()
print(code_str)
# 保存图片
with open('code.png', 'wb') as f:
img.save(f, format='png')
2.4.2 验证码输入框与验证码图片在django项目的实现
- 在登录的表单中添加验证码文本框
class login_ModelForm(BootStrapModelForm):
password = forms.CharField(widget=forms.PasswordInput, label='密码')
# 新增
code = forms.CharField(widget=forms.TextInput, label='验证码', )
class Meta:
model = models.Admin
# 新增
fields = ['username', 'password', 'code']
# 将输入的密码进行md5,加密
def clean_password(self):
password = self.cleaned_data.get('password')
MD5_pwd = md5(password)
return MD5_pwd
- 登陆页面
login.html
...
...
...
<div class="form-group">
<label>{{ form.code.label }}</label>
<div class="row">
<div class="col-xs-7">
{{ form.code }}
<span style="color: red;font-size: 10px">{{ form.code.errors.0 }}</span>
</div>
<div class="col-xs-5">
{#图片的地址直接转到生成图片的视图函数中#}
{#特别注意:cookie验证时不仅要排除登录视图函数,还得排除生成图片视图函数#}
<img id="img_code" src="/image/code" style="width: 125px">
</div>
</div>
</div>
...
...
...
-
图片验证码的增加
- 生成随机的图片验证码
app-utils-code.py
import random from PIL import Image, ImageDraw, ImageFont, ImageFilter def check_code(width=120, height=30, char_length=5, font_file='Monaco.ttf', font_size=28): code = [] # 创建一个图片 img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255)) # 创建一个画笔 draw = ImageDraw.Draw(img, mode='RGB') # 生成随机随机字母 def rndChar(): return chr(random.randint(65, 90)) # 生成随机颜色 def rndColor(): return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255)) # 将文字写入图片中 font = ImageFont.truetype(font_file, font_size) for i in range(char_length): char = rndChar() code.append(char) h = random.randint(0, 4) draw.text([i * width / char_length, h], char, font=font, fill=rndColor()) # 写干扰点 for i in range(40): draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor()) # 写干扰圆圈 for i in range(40): draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor()) x = random.randint(0, width) y = random.randint(0, height) draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor()) # 画干扰线 for i in range(5): x1 = random.randint(0, width) y1 = random.randint(0, height) x2 = random.randint(0, width) y2 = random.randint(0, height) draw.line((x1, y1, x2, y2), fill=rndColor()) img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) return img, ''.join(code)
check_code()
返回两个值img:
<class 'PIL.Image.Image'>
类型的图片code:图片验证码上的字符串
- 图片验证码的显示
<div class="col-xs-5"> <img id="img_code" src="/image/code" style="width: 125px"> </div>
图片的地址采用视图函数来实现,image_code视图函数自动接收生成的图片,加载内存中传入html中
def image_code(request): img, code_string = check_code() request.session['image_code'] = code_string # 给session设置60s超时,超过60s,自动无效 request.session.set_expiry(60) # 向内存传入图片 stream = BytesIO() img.save(stream, 'png') return HttpResponse(stream.getvalue())
- 要点1
img, code_string = check_code()
调用pillow函数生成图片img:
<class 'PIL.Image.Image'>
类型的图片code_string为图片上的文字,用于校验
- 要点2
request.session['image_code'] = code_string
验证码的校验利用session 将图片码写入自己的session中,以便于后续获取验证码进行校验
在session存储的数据也加一条 {image_code:code_string}- 要点3
request.session.set_expiry(60)
给session设置60s超时,超过60s,自动无效- 要点4
stream = BytesIO() img.save(stream, 'png') return HttpResponse(stream.getvalue())
向内存中传输图片,传入html中显示
图片的地址直接转到生成图片的视图函数中,特别注意:生成图片的视图函数也不需要cookie验证,cookie验证时不仅要排除登录视图函数,还得排除生成图片视图函数
auth.py
class AuthMiddleware(MiddlewareMixin):
def process_request(self, request)
if request.path_info in ['/login/','/image/code']:
return
# 2.获取生成cookie时存的数据
info_dic = request.session.get('info')
# 校验成功,允许进入视图函数
if info_dic:
return
# 校验失败,直接返回,不允许进入视图函数
return redirect('/login')
2.4.3 图片验证码的校验
已知在生成图片验证码时已经将图片的中的随机验证码字符串传入
request.session['image_code'] = code_string
,因此request.session中多存储一条code_string的信息。 如此,用户输入完验证码时,通过
form.cleaned_data
获取前端表单输入的数据,取出图片验证码code与session中的code_string进行对比即可
def login(request):
if request.method == 'GET':
form = login_ModelForm()
return render(request, 'login.html', {'form': form})
form = login_ModelForm(request.POST)
if form.is_valid():
# 用户输入的code 并且取出form.cleaned_data中的code,此时form.cleaned_data仅有username和password信息,使用户名和密码可以校验
in_code= form.cleaned_data.pop("code")
# 从session提取图片字母
image_code = request.session.get('image_code','')
# 判断验证码
if not (in_code.upper()==image_code):
# 在form中添加错误(错误地方,错误) '
# 将错误信息展示到密码上
form.add_error("code", "验证码错误")
return render(request, 'login.html', {'form': form})
# 判断用户名密码是否正确(是否查到)
admin_object = models.Admin.objects.filter(**form.cleaned_data).first()
if not admin_object:
form.add_error("password", "用户名或密码错误")
return render(request, 'login.html', {'form': form})
request.session['info'] = {'id': admin_object.id, 'username': admin_object.username}
# cookie 保存7天
request.session.set_expiry(60*60*60*7)
return redirect('/admin/list')
return render(request, 'login.html', {'form': form})