Django02身份驗證

Django提供了一套身份驗證和授權(quán)的權(quán)限系統(tǒng),允許驗證用戶憑證窖式,并定義每個用戶允許執(zhí)行的操作。

權(quán)限系統(tǒng)框架包括了用戶和分組的內(nèi)置模型动壤,用于登錄用戶的權(quán)限萝喘,指定用戶是否可以執(zhí)行任務、表單琼懊、視圖阁簸,以及查看限制內(nèi)容的工具。

Django身份驗證系統(tǒng)為通用設計因此不提供其它Web身份驗證系統(tǒng)中所提供的功能哼丈,對于某些常見問題可作為第三方軟件包提供启妹,比如限制登錄嘗試和針對第三方的身份驗證

登錄授權(quán)

manage應用路由設置后臺登錄URL

$ vim apps/manage/urls.py
from django.urls import path, re_path

from apps.manage.apps import ManageConfig
from .views import login, home

app_name = ManageConfig.name

urlpatterns = [
    path("", home.index, name="home"),
]

隨著功能開發(fā)views.py視圖文件中的代碼會越來越多,整個文件會越來越臃腫醉旦,不便于維護且不符合單一原則饶米。因此應該將views.py中內(nèi)容拆分到同一個目錄下。

manage應用下創(chuàng)建views文件夾同時在該目錄下創(chuàng)建__init__.py文件使其作為一個Python包package髓抑。

$ mkdir -p apps/manage/views
$ touch apps/manage/views/__init__.py

views目錄下將功能類似的方法存到同一個文件下咙崎,這里針對登錄和首頁,兩個獨立的功能模塊劃分出兩個視圖文件吨拍。

$ vim apps/manage/views/home.py
$ vim apps/manage/views/login.py

manage應用的url.py文件中使用from .views import login, home導入當前目錄下views包下的home模塊和login模塊褪猛。將登錄UI界面中涉及到操作放在login模塊下,將后臺首頁UI界面中涉及到的操作放到home模塊中羹饰。

path("", home.index, name="home")

同樣為了反轉(zhuǎn)解析URL伊滋,在URL的路由規(guī)則中添加name為當前路由設置別名。

為方便測試在視圖中直接輸出字符串文本

$ vim apps/manage/views/home.py
from django.http import HttpResponse

def index(request):
  return HttpResponse("home index")

運行測試

啟動Django開發(fā)服務器測試

  • 使用Ctrl+C快捷鍵關(guān)閉服務器
  • 使用Ctrl+Z會將服務器進程掛起端口一直會被占用队秩,重啟后會提示端口占用笑旺。
$ python3 manage.py runserver 127.0.0.1:8000
  • Windows10查找指定端口運行的進程
$ netstat -ano | findstr 8000
  TCP    127.0.0.1:8000         0.0.0.0:0              LISTENING       32036
  TCP    127.0.0.1:8000         127.0.0.1:61366        ESTABLISHED     32036
  TCP    127.0.0.1:61366        127.0.0.1:8000         ESTABLISHED     24016
  TCP    127.0.0.1:61920        127.0.0.1:8000         TIME_WAIT       0
  TCP    127.0.0.1:61983        127.0.0.1:8000         TIME_WAIT       0
  • 強制/f殺死指定PID進程及其子進程/t
$ taskkill /f /t /pid 32036
成功: 已終止 PID 32036 (屬于 PID 33980 子進程)的進程。

瀏覽器輸入后臺首頁地址測試

http://127.0.0.1:8000/manage/

項目管理后臺

Dajango自動管理后臺地址為http://127.0.0.1:8000/admin馍资,使用前需使用管理員賬號登錄筒主。

創(chuàng)建超級管理員賬號

$ python3 manage.py createsuperuser

創(chuàng)建成功后會在auth_user表中生成一條記錄,對管理員表進行進一步調(diào)整。創(chuàng)建的超級用戶已經(jīng)經(jīng)過身份驗證并擁有所有權(quán)限乌妙。

CREATE TABLE `auth_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',
  `username` varchar(150) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '用戶名',
  `password` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '密碼',
  `first_name` varchar(150) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字',
  `last_name` varchar(150) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '姓氏',
  `email` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '郵箱',
  `is_superuser` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否為超級管理員 0否 1是',
  `is_staff` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否員工 0否 1是',
  `is_active` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否激活 0否 1是',
  `date_joined` datetime(6) NOT NULL COMMENT '創(chuàng)建時間',
  `last_login` datetime(6) DEFAULT NULL COMMENT '最近登錄時間',
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='管理員';

內(nèi)置身份驗證

當使用django-admin startproject命令創(chuàng)建項目是使兔,所有必要配置都已完成,當?shù)谝淮握{(diào)用python3 manage.py migrate命令時會自動創(chuàng)建用戶和權(quán)限的數(shù)據(jù)表藤韵。

身份驗證的配置在項目配置文件settings.pyINSTALLED_APPSMIDDLEWAREA

$ vim settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

Django的django.contrib.auth.views自帶的身份授權(quán)框架中內(nèi)置了登錄視圖LoginView

創(chuàng)建用戶登錄URL調(diào)度器

  • 應用的URL調(diào)度器文件中使用app_name添加命名空間
  • 使用django.urls模塊提供的re_path方法支持路徑與路徑轉(zhuǎn)化器使用正則表達式
$ vim apps/manage/urls.py
from django.urls import path, re_path

from apps.manage.apps import ManageConfig
from .views import login, home

app_name = ManageConfig.name

urlpatterns = [
    path("", home.index, name="home"),
    re_path(r"^login/$", login.Logon.as_view(), name="login"),
    re_path(r"^authimg/$", login.authimg, name="authimg"),
    re_path(r"^logout/$", home.logout, name="logout"),
]

app_name

一個項目下的多個應用中可能存在定義同名的URL虐沥,為了避免反轉(zhuǎn)解析URL時出現(xiàn)的混淆問題,Django提供了為應用添加命名空間的方式來區(qū)分URL泽艘,使用的方式是在urls.py中添加app_name來命名當前URL所屬的應用名稱欲险。簡單來說app_name的作用是使用應用命名空間來區(qū)分不同應用的URL。

使用視圖函數(shù)views時Django會在URL解析完成后直接將request對象及URL解析器捕獲的參數(shù)匹涮,比如使用re_path中正則捕獲的未知參數(shù)或關(guān)鍵字丟給基于函數(shù)的視圖天试。但在基于類的視圖中,這些參數(shù)不能直接丟給一個類焕盟,因此就產(chǎn)生了as_view函數(shù)秋秤,as_view只做一件事兒就是返回一個閉包,這個閉包和視圖函數(shù)views一樣能夠接收URL解析器傳遞過來的參數(shù)脚翘。

path

pathre_path源碼中可以發(fā)現(xiàn)灼卢,它們都是partial類的實例,因此pathre_path并不是普通函數(shù)而是對象来农。

path = partial(_path, Pattern=RoutePattern)
re_path = partial(_path, Pattern=RegexPattern)

pathre_path執(zhí)行邏輯

當啟動Django項目時程序執(zhí)行到urlpatterns位置鞋真,urlpatterns列表中各項依次得到執(zhí)行,由于re_pathpath都是對象沃于,當對象像函數(shù)一樣調(diào)用時涩咖,其實是在調(diào)用對象的__call__方法,執(zhí)行的結(jié)果是每個pathre_path調(diào)用都會返回一個URLPattern類的實例對象(django.urls.resolves.URLPattern)繁莹。

class URLPattern:
  def __init__(self, pattern, callback, default_args=None, name=None):
    self.pattern = pattern
    self.callback = callback
    self.default_args = default_args or {}
    self.name = name

URLPattern類的__init__方法中的各個參數(shù)基本對應了傳入的pathre_path參數(shù)檩互,callback屬性包含了回調(diào)函數(shù)的引用。pathre_path執(zhí)行時自身傳入的第二個參數(shù)是as_view()立即執(zhí)行函數(shù)咨演,注意是as_view()函數(shù)而非as_view闸昨,此時as_view()會立即執(zhí)行。as_view()執(zhí)行完畢會返回一個閉包薄风,因此callback中保存的實際上是這個閉包的引用饵较。需要注意的是as_view()函數(shù)只會執(zhí)行依次,即在Django項目啟動后遭赂,之后所有請求的處理都是由as_view返回的閉包循诉,即URLPattern實例對象中的callback回調(diào)函數(shù)執(zhí)行。

當每次請求來臨時撇他,URL解析器首先會完成對URL的解析以匹配到相應的回調(diào)函數(shù)茄猫,然后立即去執(zhí)行狈蚤。

內(nèi)置登錄處理

Django內(nèi)置用戶認證系統(tǒng)django.contrib.auth模塊,使用默認創(chuàng)建的auth_user表來存儲登錄用戶數(shù)據(jù)募疮。

登錄處理需使用auth模塊的處理方法

authenticate()

authenticate方法提供了用戶認證功能炫惩,即驗證用戶名username和密碼password是否正確。

user = auth.authenticate(username="user", password="pwd")

authenticate方法如果認證成功會返回User對象阿浓,若查詢失敗則返回None

login(HttpRequest, user)

login方法接受一個HttpRequest對象以及一個經(jīng)過認證的User對象蹋绽。

auth.login(request, user)

auto.login方法執(zhí)行會做兩件事兒

  1. 完成會話操作芭毙,將用戶數(shù)據(jù)保存到數(shù)據(jù)庫,并生成隨機sessionid保存到cookie中發(fā)送給客戶端卸耘。
  2. 將驗證后的user用戶對象保存到request請求對象的request.user屬性中

只要使用auth.login(request, user)登錄操作后退敦,后續(xù)即可從request.user拿到當前登錄的用戶對象。否則request.user得到的是一個匿名用戶對象AnonymouseUser Object蚣抗,AnonymouseUserrequest.user的默認值侈百。

logout(request)

logout函數(shù)接收一個HttpRequest請求對象,無返回值翰铡。當調(diào)用logout函數(shù)時當前請求的session會話信息會全部清钝域。也就是說即使沒有登錄,執(zhí)行logout函數(shù)也不會報錯锭魔。

User

request.user.is_authenticated()

authenticate方法判斷當前user是不是一個真正的User對象例证,用于檢查用戶是否已經(jīng)通過認證,若通過返回True否則返回False迷捧。

通過認證并不意味著用戶擁有任何權(quán)限织咧,甚至不會檢查用戶是否處于激活狀態(tài),只是表名用戶成功的通過了認證漠秋。

為方便判斷用戶通過認證笙蒙,auth模塊提供了一個裝飾器工具@login_required,用來快捷地給某個視圖添加登錄檢測庆锦。

@login_required
def home(request):
  return redirect("login")

request.user.is_authenticated()出錯誤錯誤

'bool' object is not callable

錯誤原因是應該使用request.user.is_authenticated訪問屬性捅位,而非使用方法訪問。

前端模板

  • 前端圖標采用SVG類型的字體圖標 FontAwesome
<script src="https://cdn.bootcdn.net/ajax/libs/font-awesome/5.13.0/js/all.min.js"></script>
  • 前端CSS采用 TailwindCSS
<link  rel="stylesheet">
  • 前端JS采用 AlpineJS
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>

前端資源

用戶登錄
后臺首頁

用戶登錄

用戶登錄流程

  1. 進入登錄頁面用戶輸入用戶名和密碼提交登錄表單
  2. 用戶登錄視圖接收到POST過來的用戶名和密碼并認證判斷
  3. 認證判斷成功后執(zhí)行登錄操作
  4. 登錄成功重定向到首頁肥荔,登錄失敗返回登錄頁面并攜帶錯誤提示绿渣。
  5. 首頁需要判斷當前用戶是否已經(jīng)登錄,若已經(jīng)登錄則渲染視圖燕耿,否則跳轉(zhuǎn)安全退出中符。

編寫登錄URL規(guī)則

$ vim apps/manage/urls.py
from django.urls import path, re_path

from apps.manage.apps import ManageConfig
from .views import login, home

app_name = ManageConfig.name

urlpatterns = [
    re_path(r"^login/$", login.Login.as_view(), name="login"),
    re_path(r"^authimg/$", login.authimg, name="authimg"),
]

這里提供了URL地址分別是

編寫登錄視圖

$ vim apps/manage/views/login.py
from io import BytesIO

from django.contrib import auth
from django.http import HttpResponse
from django.shortcuts import render, redirect
from django.views.generic.base import View

from apps.manage.utils import Utils

# 登錄
class Login(View):
    template_name = "login.html"

    def error(self, request, message=""):
        return render(request, self.template_name, {"error":message})
    def get(self, request):
        return render(request, self.template_name)
    def post(self, request):
        username = request.POST.get("username", None)
        password = request.POST.get("password", None)
        authcode = request.POST.get("authcode", None)
        print(username, password, authcode.lower(), request.session["authcode"])
        # 圖片驗證碼
        if not authcode:
            return self.error(request, "請?zhí)顚戲炞C碼")
        # 驗證碼判斷
        if authcode.lower() != request.session["authcode"]:
            return self.error(request, "驗證碼輸入有誤")
        # 輸入判斷
        if not username or not password:
            return self.error(request, "請?zhí)顚戀~號或密碼")
        # 使用auth模塊去auth_user表查找
        user = auth.authenticate(username=username, password=password)
        if not user:
            return self.error(request, "賬號或密碼輸入有誤")
        # 執(zhí)行登錄
        auth.login(request, user)
        # 跳轉(zhuǎn)首頁
        return redirect("manage:home")


# 生成隨機圖片驗證碼
def authimg(request):
    fd = BytesIO()
    # 生成隨機圖片二維碼
    im,code = Utils.makeAuthImg()
    # 保存圖片格式
    im.save(fd, "PNG")
    # 保存驗證碼 統(tǒng)一轉(zhuǎn)化為小寫
    request.session["authcode"] = code.lower()
    # 生成圖片
    return HttpResponse(fd.getvalue())

為什么需要使用反向解析URL呢?

redirect("manage:home")

隨著功能的增加會出現(xiàn)更多地視圖誉帅,可能之前配置的正則表達式不夠準確淀散,于是就需要修改URL的正則表達式右莱。但是正則表達式一旦修改,之前與之對應的超鏈接都需要重新修改档插,這是一件非常繁瑣且容易遺漏的操作慢蜓。有沒有辦法讓連接根據(jù)正則表達式動態(tài)生成呢?這是就出現(xiàn)了反向解析郭膛。

反向解析主要用于模板中的超鏈接和視圖中的重定向晨抡。如何使用反向解析呢?首先需要在定義URL時為include定義namespace命名空間则剃,為url定義name別名屬性耘柱。在模板中使用url標簽時,在視圖中利用reverse函數(shù)根據(jù)正則表達式動態(tài)生成地址棍现,以降低后期維護成本调煎。

編寫登錄模板

$ vim apps/manage/templates/login.html
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>登錄GM游戲管理平臺</title>
    <link  rel="stylesheet">
    <script src="https://cdn.bootcdn.net/ajax/libs/font-awesome/5.13.0/js/all.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>
</head>
<body>
    <div class="h-screen flex flex-col items-center md:flex-row">
        <div class="w-full h-screen bg-black hidden lg:block md:w-1/2 xl:w-2/3">
            <img src="https://source.unsplash.com/1441x768" class="w-full h-full object-cover object-center">
        </div>
        <div class="w-full h-screen px-6 bg-white flex items-center justify-center  md:max-w-md md:mx-auto md:mx-0 md:w-1/2 lg:max-w-full lg:px-16 xl:w-1/3 xl:px-12">
            <div class="w-full h-100">
                <h1 class="text-xl font-bold text-center md:text-2xl"><i class="fas fa-dragon"></i> GM游戲管理平臺</h1>
                {% if error %}
                    <div class="relative px-4 py-3 mt-12 bg-red-100 border border-red-400 rounded text-red-700" role="alert" x-data="{showAlert:true}" x-show="showAlert">
                        <strong class="font-bold">溫馨提示</strong>
                        <span class="block sm:inline">{{ error }}</span>
                        <span class="absolute top-0 bottom-0 right-0 px-4 py-3 cursor-pointer" @click="showAlert=false">&times;</span>
                    </div>
                    {% else %}
                    <h2 class="text-xl leading-tight mt-12">歡迎使用,請輸入您的賬號和密碼己肮!</h2>
                {% endif %}
                <form action="{% url 'manage:login' %}" method="post" class="mt-6">
                    {% csrf_token %}
                    <label class="block text-gray-700" for="username">賬號</label>
                    <input type="text"
                           name="username"
                           id="username"
                           class="w-full px-4 py-3 mt-2 bg-gray-200 border rounded-lg focus:border-blue-500 focus:bg-white focus:outline-none"
                           autofocus autocomplete required/>
                    <label class="block mt-4 text-gray-700" for="password">密碼</label>
                    <input type="password"
                           name="password"
                           id="password"
                           class="w-full px-4 py-3 mt-2 bg-gray-200 border rounded-lg focus:border-blue-500 focus:bg-white focus:outline-none"
                           required/>
                    <div class="mt-2 text-right">
                        <a href="" class="text-sm text-gray-799 focus:text-blue-700 hover:text-blue-700">忘記密碼</a>
                    </div>
                    <div class="flex flex-wrap mb-6 -mx-3">
                        <div class="w-full md:w-1/2 px-3">
                            <label for="authcode" class="block mb-2 text-gray-700 tracking-wide">驗證碼</label>
                            <input type="text"
                                   name="authcode"
                                   id="authcode"
                                   class="block w-full px-4 py-3 mb-3 leading-tight bg-gray-200 appearance-none border border-gray-200 rounded-lg focus:outline-none focus:bg-white focus:border-blue-500"
                                   required/>
                        </div>
                        <div class="w-full md:w-1/2 px-3" >
                            <p class="block text-gray-500 tracking-wide mb-2">點擊圖片更換</p>
                            <img src="{% url 'manage:authimg' %}"
                                 class="max-w-full h-auto border-none align-middle"
                                 onclick="this.src = '{% url 'manage:authimg' %}'+'?_='+Math.random()"
                            />
                        </div>
                    </div>
                    <button type="submit" class="block w-full px-4 py-3 mt-6 bg-blue-500 rounded-lg text-white focus:bg-blue-400 hover:bg-blue-400">
                        登錄
                    </button>
                </form>
                <hr class="my-6 w-full border-gray-300">
                <button class="block w-full px-4 py-3 bg-white border border-gray-300 rounded-lg text-gray-900 focus:bg-gray-100 hover:bg-gray-100">
                    <div class="flex items-center justify-center">
                        <span class="ml-2">微信登錄</span>
                    </div>
                </button>
                <div class="mt-12 text-sm text-gray-500 text-center">&copy; 2020 JunChow - All Rights Reserved.</div>
            </div>
        </div>
    </div>
</body>
</html>

圖片驗證碼

目標:為登錄表單添加隨機圖片驗證碼

圖片驗證碼

創(chuàng)建隨機圖片驗證碼

為了創(chuàng)建圖片驗證碼需引入圖片處理類用生成圖片驗證碼士袄,這里采用的Python中的pillow模塊。

安裝PIL模塊

$ pip3 install pillow

生成隨機圖片驗證碼流程

  1. 創(chuàng)建畫布谎僻,并指定畫布尺寸與背景色娄柳。
  2. 創(chuàng)建畫筆
  3. 創(chuàng)建字體,并隨機設置字體戈稿。
  4. 隨機循環(huán)生成字體并使用畫筆繪制文本
  5. 隨機循環(huán)生成干擾線并使用畫筆繪制弧線
  6. 隨機循環(huán)生成干擾點并使用畫筆繪制點

字體保存位置

字體屬于全局靜態(tài)資源西土,在項目根目錄下創(chuàng)建static文件夾用于保存全局靜態(tài)資源文件,在static目錄下創(chuàng)建fonts文件夾用于保存字體文件鞍盗。

創(chuàng)建工具類

$ vim apps/manage/utils.py
import random
from io import BytesIO
from PIL import Image, ImageDraw, ImageFont

# 自定義工具類
class Utils:
    # 生成圖片驗證碼
    @staticmethod
    def makeAuthImg(len=4, width=270, height=50):
        # 定義隨機顏色生成函數(shù)
        def make_random_color(start=0, stop=255):
            r = random.randrange(start, stop)
            g = random.randrange(start, stop)
            b = random.randrange(start, stop)
            return (r, g, b)

        # 定義隨機字符生成函數(shù)
        def make_random_char():
            return random.choice([
                str(random.randint(0, 9)),
                chr(random.randint(97, 122)),
                chr(random.randint(65, 90))
            ])

        # 創(chuàng)建畫布
        canvas = Image.new(
            mode="RGB",
            size=(width, height),
            color=make_random_color(218, 255)
        )
        # 創(chuàng)建畫筆
        draw = ImageDraw.Draw(canvas, mode="RGB")
        # 創(chuàng)建字體
        font = ImageFont.truetype(
            font="static/fonts/Mogul-Arial.ttf",
            size=random.randint(int(height/3), height-10)
        )
        # 隨機生成字符
        code = ""
        for i in range(len):
            char = make_random_char()
            x = i * width / 4 + random.randint(0, 30)
            y = random.randint(0, int(height/3))
            draw.text(
                (x, y),
                char,
                make_random_color(128, 192),
                font
            )
            code += char
        # 隨機干擾線
        for i in range(len):
            x = random.randint(0, int(width/6))
            y = random.randint(0, int(height/2))
            draw.arc(
                (x, y, width-x, height-y),
                0,
                180,
                make_random_color(64, 128)
            )
        # 隨機干擾點
        for i in range(len*50):
            draw.point(
                (random.randint(0, width), random.randint(0, height)),
                fill=make_random_color(0, 64)
            )
        return canvas, code

創(chuàng)建URL

  • 通過URL地址http://127.0.0.1:8000/manage/authimg獲取圖片驗證碼
  • 注意直接訪問地址返回的將是亂碼需放在img標簽使用
$ vim apps/manage/urls.py
re_path(r"^authimg/$", login.authimg, name="authimg")

創(chuàng)建視圖

$ vim apps/manage/views/login.py
from io import BytesIO
from django.http import HttpResponse

from apps.manage.utils import Utils

# 生成隨機圖片驗證碼
def authimg(request):
    fd = BytesIO()
    # 生成隨機圖片二維碼
    im,code = Utils.makeAuthImg()
    # 保存圖片格式
    im.save(fd, "PNG")
    # 保存驗證碼 統(tǒng)一轉(zhuǎn)化為小寫
    request.session["authcode"] = code.lower()
    # 生成圖片
    return HttpResponse(fd.getvalue())

登錄模板中添加圖片驗證碼選項

  • 前端HTML添加點擊圖片更換img標簽的src屬性需了,為了保證每次請求不同,在URL后添加隨機數(shù)以示區(qū)分般甲。
$ vim apps/manage/templates/login.html
<div class="flex flex-wrap mb-6 -mx-3">
    <div class="w-full md:w-1/2 px-3">
        <label for="authcode" class="block mb-2 text-gray-700 tracking-wide">驗證碼</label>
        <input type="text"
               name="authcode"
               id="authcode"
               class="block w-full bg-gray-200 text-gray-700 appearance-none border border-gray-200 rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:border-gray-500">
    </div>
    <div class="w-full md:w-1/2 px-3" >
        <p class="block text-gray-500 tracking-wide mb-2">點擊圖片更換</p>
        <img src="{% url 'backend:authimg' %}"
             id="authimg"
             class="max-w-full h-auto border-none align-middle"
             onclick="this.src = '{% url 'manage:authimg' %}'+'?_='+Math.random()"
        />
    </div>
</div>

登錄表單提交

$ vim apps/manage/views/login.py
from io import BytesIO

from django.contrib import auth
from django.http import HttpResponse
from django.shortcuts import render, redirect
from django.views.generic.base import View

from apps.manage.utils import Utils

# 登錄
class Login(View):
    template_name = "login.html"

    def error(self, request, message=""):
        return render(request, self.template_name, {"error":message})
    def get(self, request):
        return render(request, self.template_name)
    def post(self, request):
        username = request.POST.get("username", None)
        password = request.POST.get("password", None)
        authcode = request.POST.get("authcode", None)
        print(username, password, authcode.lower(), request.session["authcode"])
        # 圖片驗證碼
        if not authcode:
            return self.error(request, "請?zhí)顚戲炞C碼")
        # 驗證碼判斷
        if authcode.lower() != request.session["authcode"]:
            return self.error(request, "驗證碼輸入有誤")
        # 輸入判斷
        if not username or not password:
            return self.error(request, "請?zhí)顚戀~號或密碼")
        # 使用auth模塊去auth_user表查找
        user = auth.authenticate(username=username, password=password)
        if not user:
            return self.error(request, "賬號或密碼輸入有誤")
        # 執(zhí)行登錄
        auth.login(request, user)
        # 跳轉(zhuǎn)首頁
        return redirect("manage:home")

前端登錄錯誤錯誤提示Alert組件

  • 使用Alphine.js處理點擊錯誤關(guān)閉按鈕隱藏提示欄
登錄錯誤Alert提示
<div class="relative px-4 py-3 mt-12 bg-red-100 border border-red-400 rounded text-red-700" role="alert" x-data="{showAlert:true}" x-show="showAlert">
    <strong class="font-bold">溫馨提示</strong>
    <span class="block sm:inline">{{ error }}</span>
    <span class="absolute top-0 bottom-0 right-0 px-4 py-3 cursor-pointer" @click="showAlert=false">&times;</span>
</div>

后臺首頁

配置首頁路由

$ vim apps/manage/urls.py
from django.urls import path, re_path

from apps.manage.apps import ManageConfig
from .views import login, home

app_name = ManageConfig.name

urlpatterns = [
    path("", home.index, name="home"),
    re_path(r"^logout/$", home.logout, name="logout"),
]

創(chuàng)建首頁視圖完成首頁登錄驗證

$ vim apps/manage/views/home.py
from django.contrib import auth
from django.shortcuts import render, redirect

# 首頁
def index(request):
    template_name = 'home.html'
    print(request.user.is_authenticated)
    # 判斷用戶是否登錄
    if(request.user.is_authenticated == False):
        return redirect("manage:logout")
    # 渲染模板
    return render(request, template_name)

# 退出
def logout(request):
    auth.logout(request)
    return redirect("manage:login")

創(chuàng)建模板

$ vim apps/manage/templates/home.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>GM游戲管理平臺</title>
    <link  rel="stylesheet">
    <script src="https://cdn.bootcdn.net/ajax/libs/font-awesome/5.13.0/js/all.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>
</head>
<body>
<!--container-->
<div class="mx-auto bg-gray-100" x-data="{dropdown:false, aside:true, menu:false}">
    <!--screen-->
    <div class="min-h-screen flex flex-col">
        <!--header-->
        <header class="relative bg-black text-white flex items-center justify-between px-4 py-1">
            <!--logo-->
            <div class="inline-flex items-center">
                <i class="fas fa-bars" @click="aside=!aside"></i>
            </div>
            <!--avatar-->
            <div class="flex flex-row items-center justify-center">
                <img src="http://source.unsplash.com/100x100/?avatar" class="h-8 h-8 rounded-full">
                <span class="p-2 hidden md:block text-xs">Admin</span>
                <i class="fas fa-caret-down" @click="dropdown=!dropdown"></i>
            </div>
            <!--downdrop-->
            <div class="absolute right-0 mt-16 mr-2 bg-white border rounded shadow-xl" x-show="dropdown">
                <ul class="list-reset divide-y text-gray-700 text-xs">
                    <li>
                        <a href="" class="no-underline block px-4 py-2 hover:bg-gray-100">個人資料</a>
                    </li>
                    <li>
                        <a href="{% url 'backend:logout' %}" class="no-underline block px-4 py-2 hover:bg-gray-100">安全退出</a>
                    </li>
                </ul>
            </div>
        </header>
        <!--main-->
        <main class="flex-1 flex">
            <!--mini-->
            <nav class="p-2 bg-black text-white flex flex-col items-center justify-star" x-show="!aside">
                <span class="mb-2 last:mb-0 w-8 h-8 rounded-full hover:bg-gray-900 flex items-center justify-center" @click="aside=!aside">
                    <i class="fas fa-users"></i>
                </span>
                <span class="mb-2 last:mb-0 w-8 h-8 rounded-full hover:bg-gray-900 flex items-center justify-center" @click="aside=!aside">
                    <i class="fas fa-cogs"></i>
                </span>
            </nav>
            <!--sidebar-->
            <aside class="bg-gray-900 text-white hidden md:block lg:block" x-show="aside">
                <!--nav-->
                <ul class="list-reset flex flex-col divide-y divide-gray-900 text-gray-400">
                    <li class="w-full h-full">
                        <!--level1-->
                        <div class="px-2 py-3 flex items-center justify-between bg-gray-800 text-base">
                            <a href="" class="no-underline block flex items-center">
                                <i class="fas fa-bug"></i>
                                <span class="ml-1 w-48">一級菜單</span>
                            </a>
                            <i class="fas fa-angle-right" @click="menu=!menu"></i>
                        </div>
                        <!--level2-->
                        <ul class="list-reset flex flex-col divide-y divide-gray-800 text-sm" x-show="menu">
                            <li class="w-full h-full">
                                <div class="py-2 pl-4 pr-2 flex items-center justify-between">
                                    <a href="" class="no-underline block">
                                        <i class="fas fa-caret-right"></i>
                                        <span class="ml-1">二級菜單</span>
                                    </a>
                                    <i class="fas fa-angle-right"></i>
                                </div>
                            </li>
                            <li class="w-full h-full">
                                <div class="py-2 pl-4 pr-2 flex items-center justify-between text-gray-500">
                                    <a href="" class="no-underline block text-sm">
                                        <i class="fas fa-caret-right"></i>
                                        <span class="ml-1">二級菜單</span>
                                    </a>
                                    <i class="fas fa-angle-right"></i>
                                </div>
                            </li>
                        </ul>
                    </li>
                </ul>
            </aside>
            <!--content-->
            <div class="flex-1 p-4 overflow-hidden">
                <!--card-->
                <div class="my-2 border border-solid border-gray-200 rounded bg-white shadow-sm w-full">
                    <div class="px-2 py-3 border-b border-gray-200 font-base">卡片標題</div>
                    <div class="p-4 text-sm">卡片內(nèi)容</div>
                </div>
            </div>
        </main>
        <!--footer-->
        <footer></footer>
    </div>
</div>
</body>
</html>

由于后臺頁面存在多個高度復用的區(qū)塊肋乍,在下一步會將模板進行抽象提取出公共部分,使用模板導入和模板加載的方式敷存,將拆分出模板碎片有機地整合在一起墓造,以增加模板文件代碼的復用。

版本控制

$ cd gamesite
$ git init

在本地項目下使用git init命令后會在項目根目錄下生成隱藏的.git文件夾

  • 上傳所有代碼到本地倉庫
$ git add .
$ git commit -m "initial commit"
  • 關(guān)聯(lián)本地倉庫和遠程倉庫
$ git remote add origin https://gitee.com/junchow/gmws.git
$ git push origin master
To https://gitee.com/junchow/gmws.git
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'https://gitee.com/junchow/gmws.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

錯誤原因遠程倉庫與本地倉庫不一致锚烦,這里由于遠程倉庫中存在.README.md文件而本地倉庫并不存在觅闽,因此需要將遠程倉庫先pull拉下來,對齊后再提交涮俄。另外本地文件中由于IDE自身的.idea文件夾隨時處于變動狀態(tài)蛉拙,需要將其設置為不提交到遠程倉庫中。

$ git pull origin master
warning: no common commits
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (5/5), done.
From https://gitee.com/junchow/gmws
 * branch            master     -> FETCH_HEAD
 * [new branch]      master     -> origin/master
fatal: refusing to merge unrelated histories

錯誤原因是由于本地倉庫和遠程倉庫兩個分支是兩個不同的版本彻亲,具有不同的提交歷史孕锄。

解決方案是添加--allow-unrelated-histories允許不相關(guān)的歷史提交吮廉,強制合并。

$ git pull origin master --allow-unrelated-histories

如果使用--rebase參數(shù)

$ git pull origin master --rebase
error: Cannot pull with rebase: You have unstaged changes.

pull實際上是fetch + merge的操作即將遠程倉庫的更新合并到本地倉庫畸肆,--rebase是取消本地倉庫最近的commit并將它們接到更新后的版本庫中宦芦。

之所以出現(xiàn)錯誤是由于git pull -rebase的工作機制是

  1. 將本地commit提交到本地倉庫的內(nèi)容,取出來放到暫存區(qū)(stash)轴脐,此時本地工作區(qū)是干凈的调卑。
  2. 從遠程拉取代碼到本地,由于工作區(qū)是干凈的大咱,因此會參數(shù)沖突令野。
  3. 從暫存區(qū)將之前提交的內(nèi)容取出來跟拉下來的代碼合并

查看GIT狀態(tài)

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   .idea/workspace.xml

no changes added to commit (use "git add" and/or "git commit -a")

遠程提交忽略文件.gitignore

.idea文件夾添加到GIT的.gitignore文件內(nèi),遠程提交時不包含idea文件夾徽级。這個做法的前提條件是當前遠程分支中并不存在.idea文件,如果已經(jīng)存在則本地設置并提交是無效的聊浅,因此需要先將本地倉庫的.idea文件刪除餐抢。

$ git rm -r --cached .idea
rm '.idea/dataSources.local.xml'
rm '.idea/dataSources.xml'
rm '.idea/dataSources/037855b3-43da-4cb1-834b-35fcad4afe26.xml'
rm '.idea/gamesite.iml'
rm '.idea/misc.xml'
rm '.idea/modules.xml'
rm '.idea/vcs.xml'
rm '.idea/workspace.xml'

本地項目添加.ignore文件

$ vim .gitignore
# Intellij Pycharm
.idea/

再次提交

git add . && git commit -m "local add gitignore" && git push
[master 186c6f5] local add gitignore
 9 files changed, 2 insertions(+), 1869 deletions(-)
 create mode 100644 .gitignore
 delete mode 100644 .idea/dataSources.local.xml
 delete mode 100644 .idea/dataSources.xml
 delete mode 100644 .idea/dataSources/037855b3-43da-4cb1-834b-35fcad4afe26.xml
 delete mode 100644 .idea/gamesite.iml
 delete mode 100644 .idea/misc.xml
 delete mode 100644 .idea/modules.xml
 delete mode 100644 .idea/vcs.xml
 delete mode 100644 .idea/workspace.xml
fatal: The current branch master has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin master

錯誤The current branch master has no upstream branch.To push the current branch and set the remote as upstream表示沒有將本地分支和遠程倉庫的分支進行關(guān)聯(lián)。使用git pull默認會上傳到origin下的master分支低匙。

解決的方案有兩種

第一種方式:保證遠程分支存在的情況下旷痕,設置本地分支追蹤遠程分支。

$ git push --set-upstream origin master
Counting objects: 3, done.
Delta compression using up to 6 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 299 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-5.0]
To https://gitee.com/junchow/gmws.git
   d95be9b..186c6f5  master -> master
Branch master set up to track remote branch master from origin.

D:\python\django\project\gamesite>git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean

查看當前項目倉庫的所有分支顽冶,紅色表示遠程分支欺抗,remotes/origin/master表示遠程的主分支。

$ git branch -a
* master
  remotes/origin/master

注意git中的origin表示關(guān)聯(lián)或克隆遠程倉庫名稱强重,git為你創(chuàng)建指向這個遠程倉庫的標簽绞呈,它會指向repository

查看指向的repository

$ git remote -v
origin  https://gitee.com/junchow/gmws.git (fetch)
origin  https://gitee.com/junchow/gmws.git (push)

第二種方式:如果遠程沒有需要關(guān)聯(lián)的分支則自動創(chuàng)建以實現(xiàn)關(guān)聯(lián)

$ git push -u origin master
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末间景,一起剝皮案震驚了整個濱河市佃声,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌倘要,老刑警劉巖圾亏,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異封拧,居然都是意外死亡志鹃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門泽西,熙熙樓的掌柜王于貴愁眉苦臉地迎上來曹铃,“玉大人,你說我怎么就攤上這事尝苇☆踔唬” “怎么了埠胖?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長淳玩。 經(jīng)常有香客問我直撤,道長,這世上最難降的妖魔是什么蜕着? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任谋竖,我火速辦了婚禮,結(jié)果婚禮上承匣,老公的妹妹穿的比我還像新娘蓖乘。我一直安慰自己,他們只是感情好韧骗,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布嘉抒。 她就那樣靜靜地躺著,像睡著了一般袍暴。 火紅的嫁衣襯著肌膚如雪些侍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天政模,我揣著相機與錄音岗宣,去河邊找鬼。 笑死淋样,一個胖子當著我的面吹牛耗式,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播趁猴,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼刊咳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了躲叼?” 一聲冷哼從身側(cè)響起芦缰,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎枫慷,沒想到半個月后让蕾,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡或听,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年探孝,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片誉裆。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡顿颅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出足丢,到底是詐尸還是另有隱情粱腻,我是刑警寧澤庇配,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站绍些,受9級特大地震影響捞慌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜柬批,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一啸澡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧氮帐,春花似錦嗅虏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至参咙,卻和暖如春冰更,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背昂勒。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留舟铜,地道東北人戈盈。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像谆刨,于是被迫代替她去往敵國和親塘娶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351