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.Adminfields = ['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自带的saultobj = 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_passwordelse: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.Adminfields = ['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_passwordelse: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_passwordelse: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',//最早日期为当前日期,无法wangqianlanguage: "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.Adminfields = ['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 responseclass M2(MiddlewareMixin):def process_request(self, request):print("m2-process_request")returndef process_response(self, request, response):print("m2-process_response")return responseclass M3(MiddlewareMixin):def process_request(self, request):print("m3-process_request")returndef process_response(self, request, response):print("m3-process_response")return response
请求与响应顺序:
m1-process_requestm2-process_requestm3-process_requestm3-process_responsem2-process_responsem1-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 responseclass M2(MiddlewareMixin): def process_request(self, request): print("m2-process_request") return def process_response(self, request, response): print("m2-process_response") return responseclass 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_requestm1-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:returnreturn redirect('/login')
- 中间件只能检验出登陆视图函数的其他函数
- request.path_info 获取当前用户请求的url 判断是否为登录视图的url
如果是登陆页面,不进行任何检验,直接进入登陆页面- 如果不是登录页面,则需要判断是否有登录信息
- 获取生成cookie时存的数据
- 获取生成cookie时存的数据
- 校验失败,直接返回,不允许进入视图函数
2.3 用户注销功能
删除存储的session信息,并返回登录页面即可
login.py
def logout(request):# 注销就是清除clearrequest.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 randomfrom PIL import Image, ImageDraw, ImageFont, ImageFilterdef 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 randomfrom PIL import Image, ImageDraw, ImageFont, ImageFilterdef 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:
类型的图片
code:图片验证码上的字符串
- 图片验证码的显示
图片的地址采用视图函数来实现,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:
类型的图片
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})