Django登入(含隨機生成圖片驗證碼)註冊例項

Bound_w發表於2018-12-21

登入,生成隨機圖片驗證碼

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

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表單功能。

 

原文地址:https://www.cnblogs.com/li-li/p/9911603.html

 

相關文章