登录、注册验证
考虑用户是自创表,还是对User表进行扩展,我们选择对User表进行扩展,这样也可以继续使用django内置auth验证用户是否登录
对于扩展表,我们要做的是如下操作
1.model
from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser): avatar=models.FileField(upload_to='avatar/',default='avatar/default.png') # 扩展字段
2.settings
AUTH_USER_MODEL='app01.UserInfo'
应用目录创建myfrom
LoginForm
from django import forms from django.conf import settings from app01 import models from django.contrib.auth import authenticate class LoginForm(forms.Form): """ 用户登录表单 """ username=forms.CharField(label='用户名',min_length=3,max_length=12, error_messages={ "required":"用户名不能为空", "min_length":"用户名不能少于3位", "max_length":"用户名最大8位" }, widget=forms.widgets.TextInput(attrs={"class":"form-control",'placeholder':'username'})) password=forms.CharField(label='密码',min_length=6,max_length=12, error_messages={ "required":"密码不能为空", "min_length":"密码不能少于6位", "max_length":"密码最大12位" }, widget=forms.widgets.PasswordInput(attrs={"class":"form-control",'placeholder':'password'})) verify_code = forms.CharField(label='验证码', min_length=5,max_length=5, error_messages={ 'required':'验证码不能为空', 'min_length':'验证码必须5位', 'max_length':'验证码必须5位', }, widget=forms.widgets.TextInput(attrs={'class': 'form-control verifycode', 'placeholder': '验证码'})) def __init__(self,request,*args,**kwargs): super().__init__(*args,**kwargs) self.request=request def clean_password(self): username = self.cleaned_data.get('username', None) password = self.cleaned_data.get('password', None) # 这里用的是auth模块,如果是自创建用户表,就需要models is_exsit = authenticate(username=username,password=password) print(is_exsit) if not is_exsit: self.add_error('password','用户名或密码不对') return password def clean_verify_code(self): """ 验证用户输入的验证码是否正确 """ verify_code = self.cleaned_data['verify_code'] if not verify_code: self.add_error('verify_code','请输入验证码') # 转变大小写,也就是做大小写忽略 if not str(verify_code).lower() == self.request.session[settings.CODE].lower(): self.add_error('verify_code', '验证码错误') return verify_code
RegForm
from django import forms from app01 import models class RegFrom(forms.Form): username=forms.CharField(label='用户名',min_length=3,max_length=12, error_messages={ "required":"用户名不能为空", "min_length":"用户名不能少于3位", "max_length":"用户名最大8位" }, widget=forms.widgets.TextInput(attrs={"class":"form-control",'placeholder':'username'})) password=forms.CharField(label='密码',min_length=6,max_length=12, error_messages={ "required":"密码不能为空", "min_length":"密码不能少于6位", "max_length":"密码最大12位" }, widget=forms.widgets.PasswordInput(attrs={"class":"form-control",'placeholder':'password'})) confirm_password=forms.CharField(label='确认密码',min_length=6,max_length=12, error_messages={ "required": "密码不能为空", "min_length": "密码不能少于6位", "max_length": "密码最大12位" }, widget=forms.widgets.PasswordInput(attrs={"class":"form-control",'placeholder':'password2'})) email=forms.EmailField(label="邮箱", error_messages={ 'required':'邮箱不能为空', 'invalid':'邮箱格式错误' }, widget=forms.widgets.EmailInput(attrs={'class':'form-control','placeholder':'email'})) def clean_username(self): username=self.cleaned_data.get('username') is_exist=models.UserInfo.objects.filter(username=username) if is_exist: self.add_error('username', '用户名已存在') return username def clean(self): password=self.cleaned_data.get('password') confirm_password=self.cleaned_data.get('confirm_password') if not password == confirm_password: self.add_error('cronfirm_password','2次密码不一致') return self.cleaned_data
视图函数VIEWS
def register(request): form = RegFrom.RegFrom() if request.method == 'POST': back_dic = {"code": 1000, 'msg': ''} # 校验数据是否合法 form = RegFrom.RegFrom(request.POST) # 判断数据是否合法 if form.is_valid(): # print(form_obj.cleaned_data) # {'username': 'liqianlong', 'password': '123', 'confirm_password': '123', 'email': '123@qq.com'} clean_data = form.cleaned_data # 将校验通过的数据字典赋值给一个变量 # 将字典里面的confirm_password键值对删除 clean_data.pop('confirm_password') # {'username': 'liqianlong', 'password': '123', 'email': '123@qq.com'} # 用户头像 file_obj = request.FILES.get('avatar') # 针对用户头像一定要判断是否传值 不能直接添加到字典里面去 if file_obj: clean_data['avatar'] = file_obj # 直接操作数据库保存数据 models.UserInfo.objects.create_user(**clean_data) back_dic['url'] = '/login/' else: back_dic['code'] = 2000 back_dic['msg'] = form.errors return JsonResponse(back_dic) return render(request,'register.html',{'form':form}) def login(request): form = LoginForm.LoginForm(request) if request.method=="POST": back_dic = {"code": 1000, 'msg': ''} form= LoginForm.LoginForm(request,request.POST) if form.is_valid(): # form验证通过,用户验证信息加入session user=authenticate(username=form.cleaned_data['username'],password=form.cleaned_data['password']) request.session[settings.SESSION_COOKIE_NAME] = {'id': user.id, 'user': user.username} if request.POST.get('keyfree', None) == '1': # 用户点点击,就是10秒,否则默认2周缓存 request.session.set_expiry(10) back_dic['url'] = '/home/' else: back_dic['code'] = 2000 back_dic['msg'] = form.errors return JsonResponse(back_dic) return render(request,"login.html",{"form":form}) def get_code(request): # 验证码 return get_codes.get_code(request)def logout(request): request.session.flush() return redirect('/login/')
模板
Login
{% load Icon %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Login</title> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css"> <link rel="stylesheet" href="/static/font-awesome-4.7.0/css/font-awesome.min.css"> <style> i { color: #4cae4c; } .verifycode { position: relative; } #id_img { position: absolute; right: 0; bottom: 0; height: 34px; width: 200px; } #id_verify_code { width: 40%; } </style> </head> <body> <!--LOgin--> <div class="container"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <h2 class="text-center">登录页面<i class="fa fa-bandcamp" aria-hidden="true"></i></h2> <form id="myform"> <div class="form-group"> {% csrf_token %} {% for formobj in form %} {% if not formobj.name == 'verify_code' %} <div class="form-group"> <label for="{{ formobj.auto_id }}">{{ formobj.label }}</label>{% Icon_html formobj %} {{ formobj }} <span class="pull-right text-danger"></span> </div> {% else %} <div class="form-group verifycode"> <label for="{{ formobj.auto_id }}">{{ formobj.label }}</label>{% Icon_html formobj %} {{ formobj }} <span class="text-danger"></span> <img src="{% url 'get_code' %}" alt="验证码" id="id_img"> </div> {% endif %} {% endfor %} </div> <div class="form-group"> <div class="checkbox"> <label> <input type="checkbox" name="keyfree" value="1">免密钥登录 </label> <abbr title="10s内无需重复登录,不选则为2周内无需重复登录。">详细</abbr> <a href="{% url 'register' %}"><span class="pull-right">注册</span></a> </div> </div> <div class="form-group"> <button type="button" class="btn btn-primary btn-block" id="id_commit">登录</button> </div> </form> </div> </div> </div> <script src="/static/js/jquery.min.js"></script> <script src="/static/js/gt.js"></script> <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script> <script> $('#id_img').click(function () { var oldVal=$(this).attr('src'); $(this).attr('src',oldVal+'?'); }); $('#id_commit').click(function () { $.ajax({ url:"/login/", type:'post', data:$('#myform').serialize(), success:function (args) { if (args.code==1000){ // 跳转到登陆页面 window.location.href = args.url }else{ // 如何将对应的错误提示展示到对应的input框下面 // forms组件渲染的标签的id值都是 id_字段名 $.each(args.msg,function (index,obj) { {#console.log(index,obj) // username ["用户名不能为空"]#} var targetId = '#id_' + index; $(targetId).next().text(obj[0]).parent().addClass('has-error') }) } } }) }); $('input').focus(function () { $(this).next().text('').parent().removeClass('has-error') }); </script> </body> </html>
Reg
{% load Icon %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css"> <link rel="stylesheet" href="/static/font-awesome-4.7.0/css/font-awesome.min.css"> <style> i { color: #4cae4c; } </style> </head> <body> <div class="container"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <h2 class="text-center">注册页面<i class="fa fa-bandcamp" aria-hidden="true"></i></h2> <form id="myform"> {% csrf_token %} {% for formobj in form %} <div class="form-group"> <label for="{{ formobj.auto_id }}">{{ formobj.label }}</label>{% Icon_html formobj %} {{ formobj }} <span class="pull-right text-danger"></span> </div> {% endfor %} <div class="form-group"> <label for="myfile"> <img src="/static/img/default.png" alt="用户头像" id="myimg" class="img-rounded" style="width: 100px"> </label> <input type="file" id="myfile" name="avatar" style="display: none"> </div> <div class="form-group"> <a href="{% url 'login' %}" class="btn btn-primary">返回登录</a> <input type="button" class="btn btn-primary pull-right" value="注册" id="id_commit"> </div> </form> </div> </div> </div> <script src="/static/js/jquery.min.js"></script> <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script> <script> $('#myfile').change(function () { // 文件阅读器对象 // 1 先生成一个文件阅读器对象 let myFileReaderObj = new FileReader(); // 2 获取用户上传的头像文件 let fileObj = $(this)[0].files[0]; // 3 将文件对象交给阅读器对象读取 myFileReaderObj.readAsDataURL(fileObj); // 异步操作 IO操作 // 4 利用文件阅读器将文件展示到前端页面 修改src属性 // 等待文件阅读器加载完毕之后再执行 myFileReaderObj.onload = function(){ $('#myimg').attr('src',myFileReaderObj.result) } }); $('#id_commit').click(function () { // 1 创建文件对象 var formDataObj=new FormData; // 2 遍历添加普通数据 $.each($('#myform').serializeArray(),function (index,obj) { formDataObj.append(obj.name,obj.value) }); // 3 添加文件数据 formDataObj.append('avatar',$('#myfile')[0].files[0]); // 4 发送ajax请求 $.ajax({ url:"{% url 'register' %}", type:'post', data:formDataObj, // 需要指定两个关键性的参数 contentType:false, processData:false, success:function (args) { if (args.code==1000){ // 跳转到登陆页面 window.location.href = args.url }else{ // 如何将对应的错误提示展示到对应的input框下面 // forms组件渲染的标签的id值都是 id_字段名 $.each(args.msg,function (index,obj) { {#console.log(index,obj) // username ["用户名不能为空"]#} var targetId = '#id_' + index; $(targetId).next().text(obj[0]).parent().addClass('has-error') }) } } }) }); $('input').focus(function () { $(this).next().text('').parent().removeClass('has-error') }) </script> </body> </html>
说明一下,模板里面 {% load Icon %},其实是给页面上加了一些图标,利用了 inclusion_tag
首先在应用里面创建templatetags,创建Icon.py
from django.template import Library register=Library() @register.inclusion_tag('Icon.html') def Icon_html(formobj): dic={ 'username':'fa-user', 'password':'fa-key', 'confirm_password':'fa-key', 'email':'fa-envelope-o', 'verify_code':'fa-code' } for i in dic: if formobj.name.strip().lower() == i.strip().lower(): tag=dic[i] return {'tag':tag}
Icom.html
<i class="fa {{ tag }}" aria-hidden="true"></i>
登录中间件判断是否登录
应用下创建middl目录,创建md_login.py
import re # from django.utils.deprecation import MiddlewareMixin from django.shortcuts import redirect from django.conf import settings class MiddlewareMixin(object): def __init__(self, get_response=None): self.get_response = get_response super(MiddlewareMixin, self).__init__() def __call__(self, request): response = None if hasattr(self, 'process_request'): response = self.process_request(request) if not response: response = self.get_response(request) if hasattr(self, 'process_response'): response = self.process_response(request, response) return response class LoginMiddle(MiddlewareMixin): def process_request(self,request): ''' 无返回值:继续执行后续中间件--django 有返回值:执行自己的process_response和上面的response request.xxx=888 request.path_info ''' current_url=request.path_info for url in settings.VALID_URL: if re.match(url,current_url): print(url,current_url) return None userinfo=request.session.get(settings.SESSION_COOKIE_NAME) if not userinfo: return redirect('/login/') else: request.userinfo=userinfo return None def process_response(self,request,response): return response
情况说明
settings
STATIC_URL = '/static/' STATICFILES_DIRS=[os.path.join(BASE_DIR,'static'),] AUTH_USER_MODEL='app01.UserInfo' # 扩展字段 CODE = 'VerCode' # 验证码,这也是要存session里面 SESSION_COOKIE_NAME = 'UserInfo' # 用户信息 VALID_URL=[ # 登录白名单 '/login/', '/admin.*', '/get_code/', '/register/', ]
验证码
应用同级创建utils目录,创建get_code.py,验证码逻辑就是生成验证码存session,跟你输入的作对比
""" 图片相关的模块 pip3 install pillow """ import os,random from PIL import Image,ImageDraw,ImageFont from django.conf import settings from django.http import HttpResponse """ Image:生成图片 ImageDraw:能够在图片上乱涂乱画 ImageFont:控制字体样式 """ from io import BytesIO,StringIO """ 内存管理器模块 BytesIO:临时帮你存储数据 返回的时候数据是二进制 StringIO:临时帮你存储数据 返回的时候数据是字符串 """ def get_code(request): # 推导步骤1:直接获取后端现成的图片二进制数据发送给前端 # with open(r'static/img/111.jpg','rb') as f: # data = f.read() # return HttpResponse(data) # 推导步骤2:利用pillow模块动态产生图片 # img_obj = Image.new('RGB',(430,35),'green') # img_obj = Image.new('RGB',(430,35),get_random()) # # 先将图片对象保存起来 # with open('xxx.png','wb') as f: # img_obj.save(f,'png') # # 再将图片对象读取出来 # with open('xxx.png','rb') as f: # data = f.read() # return HttpResponse(data) # 推导步骤3:文件存储繁琐IO操作效率低 借助于内存管理器模块 # img_obj = Image.new('RGB', (430, 35), get_random()) # io_obj = BytesIO() # 生成一个内存管理器对象 你可以看成是文件句柄 # img_obj.save(io_obj,'png') # return HttpResponse(io_obj.getvalue()) # 从内存管理器中读取二进制的图片数据返回给前端 # 最终步骤4:写图片验证码 img_obj = Image.new('RGB', (200, 34), (random.randint(0,255),random.randint(0,255),random.randint(0,255))) img_draw = ImageDraw.Draw(img_obj) # 产生一个画笔对象 font_path=os.path.join(settings.BASE_DIR,'static','font','Monaco.ttf') img_font = ImageFont.truetype(font_path,28) # 字体样式 大小 # 随机验证码 五位数的随机验证码 数字 小写字母 大写字母 code = '' for i in range(5): random_upper = chr(random.randint(65,90)) random_lower = chr(random.randint(97,122)) random_int = str(random.randint(0,9)) # 从上面三个里面随机选择一个 tmp = random.choice([random_lower,random_upper,random_int]) # 将产生的随机字符串写入到图片上 """ 为什么一个个写而不是生成好了之后再写 因为一个个写能够控制每个字体的间隙 而生成好之后再写的话 间隙就没法控制了 """ img_draw.text((10+i*40,0),tmp,(random.randint(0,255),random.randint(0,255),random.randint(0,255)),img_font) # 拼接随机字符串 code += tmp # 随机验证码在登陆的视图函数里面需要用到 要比对 所以要找地方存起来并且其他视图函数也能拿到 request.session[settings.CODE] = code io_obj = BytesIO() img_obj.save(io_obj,'png') return HttpResponse(io_obj.getvalue())
import random from PIL import Image, ImageDraw, ImageFont, ImageFilter _letter_cases = "abcdefghjkmnpqrstuvwxy" # 小写字母,去除可能干扰的i,l,o,z _upper_cases = _letter_cases.upper() # 大写字母 _numbers = ''.join(map(str, range(3, 10))) # 数字 init_chars = ''.join((_letter_cases, _upper_cases, _numbers)) def create_validate_code(size=(120, 30), chars=init_chars, img_type="GIF", mode="RGB", bg_color=(255, 255, 255), fg_color=(0, 0, 255), font_size=18, font_type="Monaco.ttf", length=4, draw_lines=True, n_line=(1, 2), draw_points=True, point_chance=2): """ @todo: 生成验证码图片 @param size: 图片的大小,格式(宽,高),默认为(120, 30) @param chars: 允许的字符集合,格式字符串 @param img_type: 图片保存的格式,默认为GIF,可选的为GIF,JPEG,TIFF,PNG @param mode: 图片模式,默认为RGB @param bg_color: 背景颜色,默认为白色 @param fg_color: 前景色,验证码字符颜色,默认为蓝色#0000FF @param font_size: 验证码字体大小 @param font_type: 验证码字体,默认为 ae_AlArabiya.ttf @param length: 验证码字符个数 @param draw_lines: 是否划干扰线 @param n_lines: 干扰线的条数范围,格式元组,默认为(1, 2),只有draw_lines为True时有效 @param draw_points: 是否画干扰点 @param point_chance: 干扰点出现的概率,大小范围[0, 100] @return: [0]: PIL Image实例 @return: [1]: 验证码图片中的字符串 """ width, height = size # 宽高 # 创建图形 img = Image.new(mode, size, bg_color) draw = ImageDraw.Draw(img) # 创建画笔 def get_chars(): """生成给定长度的字符串,返回列表格式""" return random.sample(chars, length) def create_lines(): """绘制干扰线""" line_num = random.randint(*n_line) # 干扰线条数 for i in range(line_num): # 起始点 begin = (random.randint(0, size[0]), random.randint(0, size[1])) # 结束点 end = (random.randint(0, size[0]), random.randint(0, size[1])) draw.line([begin, end], fill=(0, 0, 0)) def create_points(): """绘制干扰点""" chance = min(100, max(0, int(point_chance))) # 大小限制在[0, 100] for w in range(width): for h in range(height): tmp = random.randint(0, 100) if tmp > 100 - chance: draw.point((w, h), fill=(0, 0, 0)) def create_strs(): """绘制验证码字符""" c_chars = get_chars() strs = ' %s ' % ' '.join(c_chars) # 每个字符前后以空格隔开 font = ImageFont.truetype(font_type, font_size) font_width, font_height = font.getsize(strs) draw.text(((width - font_width) / 3, (height - font_height) / 3), strs, font=font, fill=fg_color) return ''.join(c_chars) if draw_lines: create_lines() if draw_points: create_points() strs = create_strs() # 图形扭曲参数 params = [1 - float(random.randint(1, 2)) / 100, 0, 0, 0, 1 - float(random.randint(1, 10)) / 100, float(random.randint(1, 2)) / 500, 0.001, float(random.randint(1, 2)) / 500 ] img = img.transform(size, Image.PERSPECTIVE, params) # 创建扭曲 img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) # 滤镜,边界加强(阈值更大) return img, strs
https://gitee.com/jokerbj/verification-code
效果所示
基于此我们项目一图书管理系统
https://www.cnblogs.com/jokerbj/p/13920324.html