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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM