Django - 登錄(含隨機生成圖片驗證碼)、注冊示例


一、登錄 - 隨機生成圖片驗證碼

1、隨機生成驗證碼

  Python隨機生成圖片驗證碼,需要使用PIL模塊,安裝方式如下:

  pip3 install pillow

  1)創建圖片

from PIL import Image
img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255))
with open('code.png', 'wb') as f:    # 保存在本地(即寫入硬盤)
    img.save(f, format='png')

  參數說明:

  mode='RGB'  表示以RGB來表示顏色

  size=(120,30)  表示坐標

  color=(255, 255, 255)  表示白色

  此時,打開啟動文件所在目錄,里面就有了一個寬120,高30的白色code.png圖片。

  2)創建畫筆(用於在圖片上畫任意內容)

from PIL import Image, ImageDraw
img
= Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') # 創建畫筆對象draw img.show() # 在圖片查看器中打開,這句會調用系統默認的圖片管理工具

  3)畫點 - point()方法

from PIL import Image, ImageDraw
img
= Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') # point()第一個參數:表示坐標, 第二個參數:表示顏色 draw.point([100, 20], fill='red') draw.point([60, 10], fill=(0, 255, 0)) # 保存在本地 with open('code.png', 'wb') as f: img.save(f, format='png')

效果如下圖:

  4)畫線 - line()方法

from PIL import Image, ImageDraw
img
= Image.new(mode='RGB', size=(540, 150), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') # line()第一個參數:表示起始坐標和結束坐標,第二個參數:表示顏色 draw.line((50, 50, 50, 150), fill='red') # 上面一句表示畫一條坐標(x=100,y=100)到(x=100,y=300)的直線 draw.line((50, 100, 150, 50), fill=(120, 120, 120)) draw.line((50, 50, 150, 50), fill=(0, 255, 255)) with open('code.png', 'wb') as f: img.save(f, format='png')

效果如下:

  5)畫圓 - arc()方法

from PIL import Image, ImageDraw
img
= Image.new(mode='RGB', size=(500, 140), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') # 第一個參數:表示起始坐標和結束坐標(圓要畫在其中間,兩點確定的矩形的內切圓) # 第二個參數:表示開始角度 # 第三個參數:表示結束角度 # 第四個參數:表示顏色 draw.arc((200, 20, 300, 120), 0, 360, fill='red') with open('code.png', 'wb') as f: img.save(f, format='png')

效果如下:

  6)寫文本 - text()方法

from PIL import Image, ImageDraw
img
= Image.new(mode='RGB', size=(80, 20), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') # 第一個參數:表示起始坐標,第二個參數:表示寫入的文本,第三個參數:表示顏色 draw.text([0, 0], 'python', 'red') with open('code.png', 'wb') as f: img.save(f, format='png')

效果如下:

  7)特殊字體文字(下載好引用的字體文件)

from PIL import Image, ImageDraw, ImageFont
img
= Image.new(mode='RGB', size=(120, 40), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') font = ImageFont.truetype('kumo.ttf', 28) # 第一個參數:表示字體文件路徑 # 第二個參數:表示字體大小 draw.text((0, 0), 'python', 'red', font=font) # 第一個參數:表示起始坐標 # 第二個參數:表示寫入內容 # 第三個參數:表示顏色 # 第四個參數:表示字體 with open('code.png', 'wb') as f: img.save(f, format='png')

效果如下:

  8)隨機生成圖片驗證碼

import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter

def check_code(width=120, height=30, char_length=5, font_file='../static/font/kumo.ttf', font_size=28):
    code = []
    img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
    draw = ImageDraw.Draw(img, mode='RGB')

    def rndChar():
        """
        生成隨機字符(包括大小寫字母和數字)
        :return:
        """
        ranNum = str(random.randint(0, 9))
        ranLower = chr(random.randint(65, 90))
        ranUpper = chr(random.randint(97, 120))
        return random.choice([ranNum, ranLower, ranUpper])

    def rndColor():
        """
        生成隨機顏色
        :return:
        """
        return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255))

    # 寫文字
    font = ImageFont.truetype(font_file, font_size)
    for i in range(char_length):
        char = rndChar()
        code.append(char)
        h = ( height - font_size ) / 2
        draw.text([i * width / char_length, h], char, font=font, fill=rndColor())

    # 寫干擾點
    for i in range(40):
        draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())

    # 寫干擾圓圈
    for i in range(40):
        draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
        x = random.randint(0, width)
        y = random.randint(0, height)
        draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor())

    # 畫干擾線
    for i in range(5):
        x1 = random.randint(0, width)
        y1 = random.randint(0, height)
        x2 = random.randint(0, width)
        y2 = random.randint(0, height)
        draw.line((x1, y1, x2, y2), fill=rndColor())

    # 對圖像加濾波 - 深度邊緣增強濾波
    img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)  
    return img, ''.join(code)

if __name__ == '__main__':
    # 1. 直接打開,即用圖片查看器查看
    # img,code = check_code()
    # img.show()

    # 2. 寫入文件
    # img,code = check_code()
    # with open('code.png','wb') as f:   # f是寫入磁盤的文件句柄
    #     img.save(f, format='png')
    # data = f.read()   # data是讀取圖片的字節

    # 3. 寫入內存(Python3)
    # img,code = check_code()
    # from io import BytesIO    # 內存管理的模塊
    # stream = BytesIO()         # stream是寫入內存的文件句柄
    # img.save(stream, 'png')
    # data = stream.getvalue()

    # 4. 寫入內存(Python2)
    # img,code = check_code()
    # import StringIO
    # stream = StringIO.StringIO()  # stream是寫入內存的文件句柄
    # img.save(stream, 'png')
    # data = stream.getvalue()

效果如下:

2、基於ajax實現登錄的示例代碼

  1)urls.py中關於登錄代碼:

path('login/', views.login,),  # 獲取登錄頁面url
path('get_identifyCode/', views.get_identifyCode,),  # 獲取驗證碼對應url

  2)login.html核心代碼:

<body>
<div id="particles-js">
    <div class="login">
        <p class="login-top">登錄</p>
        {% csrf_token %}
        <div class="login-center clearfix">
            <label class="" for="user">用戶名</label>
            <input type="text" id="user" placeholder="用戶名" />
        </div>

        <div class="login-center clearfix">
            <label class="iconfont labelFS" for="pwd">密碼</label>
            <input type="password" id="pwd" placeholder="密碼" />
        </div>

        <div class="login-center clearfix">
            <label class="iconfont labelFS" for="validcode">&#xe615;</label>
            <input type="text" id="validcode" placeholder="驗證碼" />
            <img src="/get_identifyCode/" alt="驗證碼" title="換一張" class="validImg" id="img" width="88" height="30" >
        </div>
        <a href="javascript:void(0);" class="login_btn">登錄</a>
        <p class="error"></p>
    </div>
</div>

<script src="jquery.min.js"></script>

<script>
    // ajax 登錄
    $(".login_btn").click(function () {
        $.ajax({
            url:"",
            type:"post",
            // data發送urlencoded格式就行,數據沒那么深,沒必要發json格式
            data:{
                 user:$("#user").val(),
                 pwd:$("#pwd").val(),
                 validcode:$("#validcode").val(),
                 csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val()
            },
            success:function (response) {
                console.log(response);
                if(response.user){
                    // 登錄成功
                    location.href="/index/"
                }
                else{
                    // 登錄失敗
                    $(".error").html(response.err_msg)
                }
            }
        })
    });

    //  驗證碼刷新:img標簽有一個天然的發請求的模式,即src的路徑后邊拼接一個問號就會發一次請求,利用這一原理可以實現驗證碼刷新
    $("#img").click(function () {
        this.src += "?"
    });

</script>
</body>

  3)views.py中獲取隨機驗證碼的視圖函數代碼(驗證碼保存利用session)

def get_identifyCode(request):
      img,code = check_code()  # 利用上面的模塊得到img對象和驗證碼code
  
    f = BytesIO()  # 得到寫入內存的文件句柄
    img.save(f, "png")   # 寫入內存
    data = f.getvalue()   # 從內存中讀出

    # 將驗證碼存在各自的session中,這樣做的好處是每個人都有自己的驗證碼,不會相互混淆(一定不能設為全局變量)
    request.session['keep_str'] = code

    return HttpResponse(data)

  4)views.py中login視圖函數代碼

from django.contrib import auth
def login(request):
    # if request.method == "POST":
    if request.is_ajax():    # 判斷是否ajax請求
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")
        validcode = request.POST.get("validcode")
        # Ajax請求通常返回一個自己構建的字典
        response={"user": None, "err_msg": ""}
          # request.session.get("keep_str")取出session中驗證碼與用戶輸入作判斷
        if validcode.upper() == request.session.get("keep_str").upper():  
            user_obj = auth.authenticate(username=user, password=pwd)
            print("user_obj", user_obj, bool(user_obj))
            if user_obj:
                response["user"] = user
                auth.login(request, user_obj)  # 保存用戶狀態
            else:
                response['err_msg'] = "用戶名或者密碼錯誤!"
        else:
            response["err_msg"] = "驗證碼錯誤!"
        return JsonResponse(response)
    else:
        return render(request, "login.html")

二、基於ajax和forms組件實現注冊示例

1)model.py

from django.contrib.auth.models import AbstractUser

class UserInfo(AbstractUser):  # 將原生auth_user表擴展一個tel手機號字段
    tel=models.CharField(max_length=32)

2)forms.py(其實代碼放在哪里沒關系,最重要的是程序能找到,為了解耦,我們可以定義一個form.py)

from django import forms
# exceptions中存着django的所有錯誤,錯誤在核心組件中
from django.core.exceptions import ValidationError
from django.forms import widgets
from app01.models import UserInfo

class UserForm(forms.Form):   # UserForm中定義需要校驗的字段
    username=forms.CharField(min_length=5,
                  label="用戶名")
    password=forms.CharField(min_length=5,
                 widget=widgets.PasswordInput(),
                 label="密碼")
    r_pwd=forms.CharField(min_length=5,
                   widget=widgets.PasswordInput(),
                   label="確認密碼")
    email=forms.EmailField(min_length=5,
                label="郵箱")

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields.values():
            field.widget.attrs.update({'class': 'form-control'}) # 統一加class

    def clean_user(self):
        val=self.cleaned_data.get("username")
        user=UserInfo.objects.filter(username=val).first()
        if user:
            raise ValidationError("用戶已存在!")
        else:
            return val

    def clean_pwd(self):
        val=self.cleaned_data.get("password")
        if val.isdigit():
            raise ValidationError("密碼不能是純數字!")
        else:
            return val

    def clean_email(self):
        val = self.cleaned_data.get("email")
        if re.search("\w+@163.com$", val):
            return val
        else:
            raise ValidationError("郵箱必須是163郵箱!")
        
    def clean(self):
        pwd=self.cleaned_data.get("password")
        r_pwd=self.cleaned_data.get("r_pwd")

        if pwd and r_pwd and r_pwd!=pwd:
            self.add_error("r_pwd", ValidationError("兩次密碼不一致!"))
        else:
            return self.cleaned_data

3)reg.html核心代碼:

<body>
<h3>注冊頁面</h3>
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <form action="" method="">
            {% csrf_token %}
            {% for field in form %}
                <div class="form-group">
                    <label for="">{{ field.label }}</label>
                    {{ field }}
                    <span class="error"></span>
                </div>
            {% endfor %}
            <input type="button" class="btn btn-primary reg_btn" value="注冊">
            </form>
        </div>
    </div>
</div>
<script src="jquery.min.js"></script>
<script>
    $(".reg_btn").click(function () {
        $.ajax({
            url:"",
            type:"post",
            data:{
                username:$("#id_username").val(),
                password:$("#id_password").val(),
                r_pwd:$("#id_r_pwd").val(),
                email:$("#id_email").val(),
                csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val()

            },
            success:function (res) {
                if (res.user){
                    // 注冊成功
                    location.href="/login/"
                }
                else{
                    // 清除錯誤
                    $(".error").html("");
                    //  展示新的錯誤
                    $.each(res.err_msg,function (i,j) {
                       $("#id_"+i).next().html(j[0]);
                    })
                }
            }
        })
    })
</script>
</body>

4)views.py中注冊的視圖函數reg

def reg(request):
    if request.method == "POST":
        form = UserInfo(request.POST)
        res = {"user": None, "err_msg": ""}
        if form.is_valid():
            res["user"] = form.cleaned_data.get("username")
            del form.cleaned_data["r_pwd"]   # 因表中無此字段,只需校驗,不插入
            UserInfo.objects.create_user(**form.cleaned_data)
        else:
            res["err_msg"] =form.errors
        return JsonResponse(res)
    else:   # get請求
        form = UserInfoModelForm()
        return  render(request,"reg.html",{"form": form})

三、補充知識點

1、對原生auth_user表擴展字段(使用AbstractUser)

  我們之前學習用戶認證組件時,用的是django提供的auth_user表,即通過引入User對象(from django.contrib.auth.models import User)去操作它,我們又發現源碼中User類繼承了AbstractUser類,所以AbstractUser和User其實就是一張表,所以當我們想要有用戶認證功能,又想要一些auth_user表中沒有的字段時,可以按照如下這樣做:

  在models.py中,引入AbstractUser,並且自己定義一個用戶類(表),這時類中只定義django的auth_user表中沒有而你又想使用的字段即可,並且讓你定義的類繼承AbstractUser,如下:

from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser): tel=models.CharField(max_length=32) # 擴展了一個手機號字段

  注意:寫完以上代碼就直接去遷移數據庫會報錯“HINT: Add or change a related_name argument to the definition for ......”,我們需要在settings.py中加上下面這句話,來告訴Django我們要使用自己定義的表作為用戶認證表(因此登錄的使用方法不變,認證時django會自己去找這張表):

AUTH_USER_MODEL="app01.UserInfo"

  這時再去進行數據庫遷移,我們發現,數據庫中沒有auth_user表了,而我們自己定義的表中除了有自己定義的那些字段外,還有之前auth_user表中的所有字段,這就代表已經達到了我們的目的。

  補充:通過命令創建超級用戶的方式:

Tools -- > Run manage.py Task    # 運行起來manage.py,再輸入如下命令
manage.py@myproject > createsuperuser   # 執行后根據提示輸入用戶名,密碼,郵箱
# 注意:輸入的密碼會進行加密處理,再存入表中,並且命令輸入密碼要求最少8位
2、JsonResponse的使用

我們發現,一般瀏覽器發送Ajax請求給服務器時,都會返回一個字典,我們需要先將字典序列化,瀏覽器接收到后再進行反序列化,你會不會覺得這樣做有點繁瑣?其實,django為我們提供了一個JsonResponse類,它為我們做好了json的序列化,並且瀏覽器接收到之后,ajax也會自動為我們反序列化,即ajax中success函數接收到的response就是反序列化之后的數據,直接使用即可,如上面登錄示例部分代碼:

from django.http import JsonResponse      # 引入JsonResponse
def login(request):
        if request.is_ajax():   
            ......            
            response={"user":None, "err_msg": ""}
            ......
            return JsonResponse(response)

  分析原因:JsonResponse本質也繼承了HttpResponse,而且既為我們做了序列化的操作,還將數據格式設置為json,ajax收到設置了json格式的數據也會為我們自動反序列化,也說明了不僅僅請求頭中有content-type,響應頭中也有,JsonResponse源碼如下:

class JsonResponse(HttpResponse):
    def __init__(self, data, encoder=DjangoJSONEncoder, safe=True,
                 json_dumps_params=None, **kwargs):
        if safe and not isinstance(data, dict):
            raise TypeError(
                'In order to allow non-dict objects to be serialized set the '
                'safe parameter to False.'
            )
        if json_dumps_params is None:
            json_dumps_params = {}
        kwargs.setdefault('content_type', 'application/json')
        data = json.dumps(data, cls=encoder, **json_dumps_params)
        super().__init__(content=data, **kwargs)
3、forms組件中對渲染出來的input輸入框統一增加一個類名

  我們在學習forms組件時,可以分別給每個字段設置一個類名,如class="form-control",但發現像之前那樣寫的有代碼冗余的問題,按照如下方式寫可以解決此問題:

from django import forms
from django.forms import widgets
class UserForm(forms.Form):
    user=forms.CharField(min_length=5,
                 label="用戶名")
    pwd=forms.CharField(min_length=5,
                 widget=widgets.PasswordInput(),
                 label="密碼")
    r_pwd=forms.CharField(min_length=5,
                 widget=widgets.PasswordInput(),
                 label="確認密碼")
    email=forms.EmailField(min_length=5,
                 label="郵箱")

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for filed in self.fields.values():
            filed.widget.attrs.update({'class': 'form-control'})
4、關於全局鈎子__all__的問題

我們知道全局鈎子的錯誤信息都在__all__中,源碼中是這樣寫的:

所以知道了這些,我們可以自己設置全局鈎子的字段,避免跟其他字段規律不一致造成單獨判斷的問題,如下方式:

  # 全局鈎子:校驗兩次密碼不一致
  def clean(self):
      pwd=self.cleaned_data.get("pwd")
      r_pwd=self.cleaned_data.get("r_pwd")

      if pwd and r_pwd and r_pwd!=pwd:
          self.add_error("r_pwd", ValidationError("兩次密碼不一致!")) # 自己定義錯誤信息對應的字段是r_pwd
      else:
          return self.cleaned_data
5、關於具有提交功能的按鈕問題

我們知道form表單是瀏覽器向服務器發請求的一種方式,提交按鈕也有多種,但是要注意,具有提交功能的按鈕有兩種:<input type="submit" value="提交" />和<button>提交</button>,也就是說,當你想用form表單發請求時,可以用以上兩種的任一種,但是當你想基於ajax發送請求時,若有form標簽,則一定不要用以上兩種提交按鈕,否則當你點擊按鈕發送ajax時會自動以form表單的方式再發一次請求,使用<input type="button" value="提交" />是可以的,因為它沒有提交form表單功能。

 


免責聲明!

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



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