一、項目前端模板的套用
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
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>
注意:
該頁面中的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文件中。

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

{% 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>