Django实战【三】—用户登录、注册实现
一、项目前端模板的套用
1.为什么使用前端模板
因为我们开发ObCRM系统使用的是Django项目,而我们知道,Django框架是一个全面的重量级的框架,并不是全后端分离的,所以涉及到的页面需要我们用到前端的知识,但是我们并不是专业的前端工程师,很多页面的搭建对我们说还是有点吃力。
其实在很多实际工作中开发,前端页面都是通过网络上寻找相应的模板,这样就可以避免在前端样式上调整的时间浪费,我们只需要继承别人写好的模板文件来快速开发自己的前端页面,专心投入自己的后端开发代码中。
2.admin前端模板
刚我们说使用别人写好的模板文件,有利于我们快速开发项目;我们写的这个项目也是使用了git上一个开源的前端模板,大家可以去git搜索adminLTE,如果你没有用过,点击这里https://github.com/ColorlibHQ/AdminLTE,将代码下载到本地就可以愉快的使用了。
我们解压后得到一个AdminLTE-master文件夹,文件夹路径结构如下:
目录下的文件内容如上,一般比较规范的模板文件,核心的js和css代码会放在dist路径下。
首先,我们先配置项目使用的静态文件夹,在项目路径下新建statics文件夹,settings中配置静态文件的路径,
STATIC_URL = '/static/' STATICFILES_DIRS = [ # 项目静态文件的配置 os.path.join(BASE_DIR,"statics") ]
其实我们使用模板文件就是使用它的核心js和css以及对应写好的html页面,我们这个项目中使用了adminLTE的一些其他html页面,也就是说还需要使用bowser_components、plugins文件,这里我就将这三个文件拷贝到statics下的新建adminlte文件夹下。
如下是我这个项目的静态文件目录结构
- adminlte:用于存放模板的js和css文件
- bootstrap:存放的bootstrap核心js和css
- jquery:存放jquery文件
- css:自己根据具体需求写的一些css文件
- js:自己根据具体需求写的一些js文件
- font:项目用到的字体文件
二、登录页面的实现
1.初始超级用户
还记得我们的ObCRM系统的用户表使用是django项目中user的扩展表把,为什么使用Django用户提供的user表来扩展呢?是因为我们想要使用Django框架内置的auth认证模块,auth认证模块可以密文存储用户密码,快速简便的查询用户信息进行验证。
auth模块知识点回顾点这里!:Django框架—auth认证模块
在实现登录功能之前,我们先要有一个用户账号,就作为我们项目的超级管理帐号把。创建账号的时候要注意,不能直接在数据库添加记录,因为这样账号密码是明文存储的。
创建超级用户的方式,是在django的manage环境下,执行如下命令
createsuperuser # 要通过这个指令来创建用户,因为这个指令会将你的密码加密
我们创建一个超级用户,这里我创建的用户帐号密码:ryxiong,ryxiong520
2.登录页面实现
登录url设置
因为我么项目涉及到多应用,所以需要使用到路由分发,用户登录输入rbac应用,所以我们在项目下url设置路由分发,将用户登录的url分发到rbac应用下。
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^rbac/', include("rbac.urls")), url(r'^customer/', include("customer.urls")), url(r'^education/', include("education.urls")), ]
rbac应用下创建urls.py
注意:我们项目中实现验证码登录的功能,所以配置登录url还需要配置验证码url
from django.conf.urls import url,include from rbac.views import account urlpatterns = [ # 用户登录url url(r'^login/', account.Login.as_view(), name="login"), # 验证码url url(r'^get_auth_img/', account.GetAuthImg.as_view(), name="get_auth_img"), ]
视图函数
对于登录用到的验证码获取我单独提炼了一个文件,放在rbac应用下的utils文件中的auth_code.py文件
get_authcode_img函数的作用是生成随机字符串,并通过内存操作符中读取出来,存储在session中,这样每个用户就可以根据携带自己的用户验证码请求后端,而不会和别人的验证码冲突。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import os
import string
import random
from io import BytesIO
from PIL import Image,ImageDraw,ImageFont
from ObCRM import settings # 项目中的settings文件,配置了BASEDIR路径,在这里无需关注
def get_authcode_img(request):
"""
获取随机验证码,带干扰噪点,
:param request: request请求,用于将验证码存放在session中
:return: 返回验证码图片的数据内容
"""
def get_background_color(): # 定义一个获取图片背景/噪点颜色的函数,产生浅色
color = tuple((random.choices(range(160,256),k=3)))
return color
def get_content_color(): # 定义一个获取文字颜色的函数,产生深色
color = tuple((random.choices(range(0,100),k=3)))
return color
img_obj = Image.new("RGB",(117,34),get_background_color()) # 创建一个图片对象
draw_obj = ImageDraw.Draw(img_obj) # 通过图片对象生成一个画笔对象
font_path = os.path.join(settings.BASE_DIR,"statics","font","cerepf__.ttf") # 获取字体,注意有些字体无法显示数字
font_obj = ImageFont.truetype(font_path,32) # 创建一个字体对象
random_code = '' # 用户验证的字符串
all_char = string.ascii_letters+string.digits
for i in range(4):
a = random.choice(all_char)
random_code += a
draw_obj.text((22,-3),random_code,fill=get_content_color(),font=font_obj)
width = 117
height = 34
# 添加噪线
for i in range(5): # 添加5条干扰线
# 两个坐标点确定一条线
x1 = random.randint(0,width)
y1 = random.randint(0,height)
x2 = random.randint(0,width)
y2 = random.randint(0,height)
draw_obj.line((x1,y1,x2,y2),fill=get_background_color()) # 画噪线
# 添加噪点
for i in range(30):
draw_obj.point((random.randint(0,width),random.randint(0,height)),fill=get_background_color())
f = BytesIO() # 生成内存操作符-句柄
img_obj.save(f,"png") # 将图片存在内存中
data = f.getvalue()
# 获取句柄中的内容
# # 存验证码方式:1.存在全局变量(不可取,多个用户会顶替)2.存在各自客户的session中
# # 方式1
# global valid_str
# valid_str = random_code
# 方式二,推荐
request.session["authcode"] = random_code
return data
rbac视图文件,我们在应用下创建了views文件夹,文件中新建account来处理帐号相关的视图。
from django import views from django.contrib import auth from django.http import JsonResponse from django.shortcuts import ( render, redirect, reverse, HttpResponse ) from rbac.utils import authcode # 用户登录视图类 class Login(views.View): def get(self, request): # get请求返回登录页面 return render(request, "login.html") def post(self, request): data = request.POST # 获取用户登录信息 authcode = data.get("authcode") username = data.get("username") password = data.get("password") # 验证码不正确 if request.session.get("authcode").upper() != authcode.upper(): return JsonResponse({"status": "1"}) else: # 使用django的auth模块进行用户名密码验证 user = auth.authenticate(username=username, password=password) if user: # 将用户名存入session中 request.session["user"] = username auth.login(request, user) # 将用户信息添加到session中 return JsonResponse({"status": "2"}) else: return JsonResponse({"status": "3"}) # 验证码视图类 class GetAuthImg(views.View): """获取验证码视图类""" def get(self, request): data = authcode.get_authcode_img(request) print("验证码:",request.session.get("authcode")) return HttpResponse(data)
模板文件
系统的登录功能是通过ajax实现异步请求,后端验证数据,这种方式可以提高用户体验。
其次,登录页面使用的是adminLTE模板中的login页面,对页面进行自己的修改,login页面在adminLTE-master/pages/examples/login.html自取。
登录html页面,放在rbac应用下新建templates文件夹下。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% load static %} <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>AliCRM | 登录</title> <!-- Tell the browser to be responsive to screen width --> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <!-- Bootstrap 3.3.7 --> <link rel="stylesheet" href="{% static 'adminlte/bower_components/bootstrap/dist/css/bootstrap.min.css' %}"> <!-- Font Awesome --> <link rel="stylesheet" href="{% static 'adminlte/bower_components/font-awesome/css/font-awesome.min.css' %}"> <!-- Ionicons --> <link rel="stylesheet" href="{% static 'adminlte/bower_components/Ionicons/css/ionicons.min.css' %}"> <!-- Theme style --> <link rel="stylesheet" href="{% static 'adminlte/dist/css/AdminLTE.min.css' %}"> <!-- iCheck --> <link rel="stylesheet" href="{% static 'adminlte/plugins/iCheck/square/blue.css' %}"> </head> <body class="hold-transition login-page"> <div class="login-box"> <div class="login-logo"> <a href=""><b>Ali</b>CRM</a> </div> <!-- /.login-logo --> <div class="login-box-body"> <p class="login-box-msg">请登录</p> <form action="" method="post"> {% csrf_token %} <div class="form-group has-feedback"> <input type="text" class="form-control" placeholder="username" name="username"> <span class="glyphicon glyphicon-user form-control-feedback"></span> <span class="username-error" style="color:#b14442"></span> </div> <div class="form-group has-feedback"> <input type="password" class="form-control" placeholder="Password" name="password"> <span class="glyphicon glyphicon-lock form-control-feedback"></span> <span class="password-error" style="color:#b14442"></span> </div> <div class="row"> <div class="col-sm-7"> <div class="form-group has-feedback"> <input type="text" class="form-control" placeholder="验证码" name="authcode"> <span class="glyphicon glyphicon-barcode form-control-feedback"></span> </div> </div> <div class="col-sm-4"> <img id="authImg" src="{% url 'get_auth_img' %}" alt="验证码"> </div> </div> <div class="row"> <div class="col-xs-8"> <div class="checkbox icheck"> <label> <input type="checkbox"> 是否记住帐号 </label> </div> </div> <!-- /.col --> <div class="col-xs-4"> <button type="button" id="loginBtn" class="btn btn-primary btn-block btn-flat">登录</button> </div> <!-- /.col --> </div> </form> <div class="social-auth-links text-center"> <p>- OR -</p> <a href="#" class="btn btn-block btn-social btn-facebook btn-flat"> <i class="fa fa-facebook"></i>使用微信登录 </a> </div> <!-- /.social-auth-links --> <a href="#">忘记密码</a><br> <a href="{% url 'register' %}" class="text-center">注册一个新账号!</a> </div> <!-- /.login-box-body --> </div> <!-- /.login-box --> <!-- jQuery 3 --> <script src="{% static 'jquery/jquery-3.4.1.js' %}"></script> <!-- Bootstrap 3.3.7 --> <script src="{% static 'adminlte/bower_components/bootstrap/dist/js/bootstrap.min.js' %}"></script> <!-- iCheck --> <script src="{% static 'adminlte/plugins/iCheck/icheck.min.js' %}"></script> <script> $(function () { $('input').iCheck({ checkboxClass: 'icheckbox_square-blue', radioClass: 'iradio_square-blue', increaseArea: '20%' /* optional */ }); }); // 登录ajax请求 $("#loginBtn").on("click", function () { var username = $("input[name=username]").val(); var password = $("input[name=password]").val(); var authcode = $("input[name=authcode]").val(); var csrf_token = $("input[name=csrfmiddlewaretoken]").val(); if (!username) { $(".username-error").text("帐号不能为空!") } if (!password) { $(".password-error").text("密码不能为空!") } if (!authcode) { $(".authcode-error").text("请输入验证码") } $.ajax({ url: "{% url 'login' %}", type: 'post', data: { username: username, password: password, authcode: authcode, csrfmiddlewaretoken: csrf_token, }, success: function (res) { if (res.status === "1") { $(".authcode-error").text("验证码错误!") } if (res.status === "2") { var href = location.search.slice(6); //使用了auth的装饰器,会记录未登录用户想要访问的登录页面,登录成功后,会自动跳转过去 if (href) { location.href = href //登录成功,有目标地址 } else { location.href = "{% url 'index' %}" // 登录成功没有目标地址,跳转主页 } } if (res.status === "3") { // 帐号密码错误 $(".username-error").text("账号或密码错误!") } } }) }); // 验证码刷新 $("#authImg").on("click", function () { $("#authImg")[0].src += "?" // 点击事件刷新验证码图片 }) </script> </body> </html>
注意:
该页面中的register和主页index还没有实现,所以路径反向解析会报错,可以先删除,以后在配置。
三、注册功能的实现
注册用户url配置
# 用户注册url url(r'^register/', account.Register.as_view(), name="register"),
注册视图类
注册视图涉及到前端提取数据到后端,并需要保存在数据库,所以数据需要校验合法性,数据的合法性校验有两种方式。
- 在前端通过js中的re正则方式去校验
- 在后端校验数据的合法性,通过form组件
由于我们的前端水平啊,不堪一说,对我们来说也相对复杂,所以我们这里通过后端来校验数据的合法性,合法性校验也可以自己获取数据区较验,但是使用form组件来完成这个事情,更加高效,快速,准确。
既然要使用form那就需要先写一个form组件,这里关于rbac的form我们放在rbac/forms/formAuth.py文件中。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django import forms from django.core.validators import RegexValidator from django.core.exceptions import ValidationError # 注册form认证 class RegForm(forms.Form): """定义注册帐号的form组件""" username = forms.CharField( label = "用户名", max_length=18, error_messages={ "required":"内容不能为空", "invalid":"格式错误", "max_length":"用户名最长不超过18位" }, widget=forms.TextInput(attrs={"class":"forms-control"}) ) password = forms.CharField( min_length=6, error_messages={ "required": "内容不能为空", "invalid": "格式错误", "min_length": "密码不能少于6位" } ) r_password = forms.CharField( min_length=6, error_messages={ "required": "内容不能为空", "invalid": "格式错误", "min_length": "密码不能少于6位" } ) email = forms.CharField( label="邮箱", error_messages={ "required": "内容不能为空", "invalid": "格式错误", }, validators=[RegexValidator(r"^\w+@\w+\.com$", "邮箱格式不正确")] ) phone = forms.CharField( label="电话", error_messages={ "required": "内容不能为空", "invalid": "格式错误", }, validators=[RegexValidator(r"^[0-9]{4,11}$","请输入正确的号码")] ) # 定义局部钩子 def clean_password(self): # 校验密码的合法性,不能为纯数据 password = self.cleaned_data.get("password") if password.isdecimal(): raise ValidationError("密码不能为纯数字!") return password def clean_r_password(self): # 校验密码的合法性,不能为纯数据 r_password = self.cleaned_data.get("r_password") if r_password.isdecimal(): raise ValidationError("密码不能为纯数字!") return r_password # 定义全局钩子 def clean(self): # 校验两次密码输入是否一致 if self.cleaned_data.get("password") != self.cleaned_data.get("r_password"): self.add_error("r_password","两次密码输入不一致!") else: return self.cleaned_data # 重写init方法,来批量设置标签的样式 def __init__(self,*args,**kwargs): super().__init__(*args,**kwargs) for field in self.fields: self.fields[field].widget.attrs.update({"class":"forms-control"})
注册视图函数
from rbac.forms import formAuth # 注册视图类 class Register(views.View): def get(self, request): # 注册页面的生成我们并没有用form,因为我们使用的别人的模板样式 return render(request, "register.html") def post(self, request): # post请求提交注册数据 data = request.POST form_obj = formAuth.RegForm(data) # 数据交给form实例化 if form_obj.is_valid(): # 验证提交数据的合法性 valid_data = form_obj.cleaned_data username = valid_data.get("username") # 判断帐号是否已存在 if models.UserInfo.objects.filter(username=username): # 如果存在,给form中的username字段添加一个错误提示。 form_obj.add_error("username", "帐号已存在") return render(request, "register.html", {"form_obj": form_obj}) else: # 帐号可用,去掉多余密码,在数据库创建记录 del valid_data["r_password"] models.UserInfo.objects.create_user(**valid_data) # 创建普通用户 return redirect("login") else: # 数据验证不通过,返回页面和错误提示,保留数据 return render(request, "register.html", {"form_obj": form_obj})
注册html页面
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% load static %} <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>AdminLTE 2 | Registration Page</title> <!-- Tell the browser to be responsive to screen width --> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <!-- Bootstrap 3.3.7 --> <link rel="stylesheet" href="{% static 'adminlte/bower_components/bootstrap/dist/css/bootstrap.min.css' %}"> <!-- Font Awesome --> <link rel="stylesheet" href="{% static 'adminlte/bower_components/font-awesome/css/font-awesome.min.css' %}"> <!-- Ionicons --> <link rel="stylesheet" href="{% static 'adminlte/bower_components/Ionicons/css/ionicons.min.css' %}"> <!-- Theme style --> <link rel="stylesheet" href="{% static 'adminlte/dist/css/AdminLTE.min.css' %}"> <!-- iCheck --> <link rel="stylesheet" href="{% static 'adminlte/plugins/iCheck/square/blue.css' %}"> </head> <body class="hold-transition"> <div class="register-box"> <div class="register-logo"> <a href=""><b>Ali</b><span class="small">CRM</span></a> </div> <div class="register-box-body" style="border: #bbb 1px solid;border-radius: 2px"> <p class="login-box-msg">注册一个帐号</p> <form action=".{% url 'register' %}" method="post" novalidate> {% csrf_token %} <div class="form-group has-feedback"> <input type="text" class="form-control" placeholder="用户名" name="username"> <span class="glyphicon glyphicon-user form-control-feedback"></span> <span class="has-error">{{ form_obj.username.errors.0 }}</span> </div> <div class="form-group has-feedback"> <input type="password" class="form-control" placeholder="密码" name="password"> <span class="glyphicon glyphicon-lock form-control-feedback"></span> <span class="has-error">{{ form_obj.password.errors.0 }}</span> </div> <div class="form-group has-feedback"> <input type="password" class="form-control" placeholder="确认密码" name="r_password"> <span class="glyphicon glyphicon-log-in form-control-feedback"></span> <span class="has-error">{{ form_obj.r_password.errors.0 }}</span> </div> <div class="form-group has-feedback"> <input type="email" class="form-control" placeholder="邮箱" name="email"> <span class="glyphicon glyphicon-envelope form-control-feedback"></span> <span class="has-error">{{ form_obj.email.errors.0 }}</span> </div> <div class="form-group has-feedback"> <input type="text" class="form-control" placeholder="电话" name="phone"> <span class="glyphicon glyphicon-phone form-control-feedback"></span> </div> <div class="row"> <div class="col-xs-8"> <div class="checkbox icheck"> <label> <input type="checkbox">我同意该<a href="#">条款</a> </label> </div> </div> <!-- /.col --> <div class="col-xs-4"> <button type="submit" class="btn btn-primary btn-block btn-flat">注册</button> </div> <!-- /.col --> </div> </form> <div class="social-auth-links text-center"> <p>- OR -</p> <a href="#" class="btn btn-block btn-social btn-success btn-flat"><span class="glyphicon glyphicon-qrcode"></span><i class=""></i>微信登录</a> </div> <a href="{% url 'login' %}" class="text-center">已经拥有账号</a> </div> <!-- /.form-box --> </div> <!-- /.register-box --> <!-- jQuery 3 --> <script src="{% static 'adminlte/bower_components/jquery/dist/jquery.min.js' %}"></script> <!-- Bootstrap 3.3.7 --> <script src="{% static 'adminlte/bower_components/bootstrap/dist/js/bootstrap.min.js' %}"></script> <!-- iCheck --> <script src="{% static 'adminlte/plugins/iCheck/icheck.min.js' %}"></script> <script> $(function () { $('input').iCheck({ checkboxClass: 'icheckbox_square-blue', radioClass: 'iradio_square-blue', increaseArea: '20%' /* optional */ }); }); </script> </body> </html>