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中,这样每个用户就可以根据携带自己的用户验证码请求后端,而不会和别人的验证码冲突。

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
authcode

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文件夹下。

{% 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>
login.html文件

注意:

该页面中的register和主页index还没有实现,所以路径反向解析会报错,可以先删除,以后在配置。

三、注册功能的实现

 注册用户url配置

# 用户注册url
url(r'^register/', account.Register.as_view(), name="register"),

注册视图类

注册视图涉及到前端提取数据到后端,并需要保存在数据库,所以数据需要校验合法性,数据的合法性校验有两种方式。

  1. 在前端通过js中的re正则方式去校验
  2. 在后端校验数据的合法性,通过form组件

由于我们的前端水平啊,不堪一说,对我们来说也相对复杂,所以我们这里通过后端来校验数据的合法性,合法性校验也可以自己获取数据区较验,但是使用form组件来完成这个事情,更加高效,快速,准确。

既然要使用form那就需要先写一个form组件,这里关于rbac的form我们放在rbac/forms/formAuth.py文件中。

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"})
formAuth.py文件

注册视图函数

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页面

{% 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>
register.html

 

posted @ 2019-06-24 18:12  ryxiong728  阅读(19024)  评论(0编辑  收藏  举报