主頁 > 後端開發 > Form組件、auth認證組件、自定義圖片驗證碼登錄、 自定義分頁

Form組件、auth認證組件、自定義圖片驗證碼登錄、 自定義分頁

2020-10-15 20:42:37 後端開發

12.7 Form組件

Django form組件實作的功能:生成頁面可用的HTML標簽、對用戶提交的資料進行校驗、保留上次輸入內容

12.71 使用form組件實作注冊功能

views.py:

# 按照Django form組件的要求寫一個類
from django import forms
from django.forms import fields
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
# 自定義驗證規則
def passward_validate(value):
    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
    if not mobile_re.match(value):
        raise ValidationError('手機號碼格式錯誤')
        
class RegForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用戶名",
        initial="張三"        # 設定默認值,初始值,input框里面的初始值
        error_messages={      #重寫錯誤資訊,
            "required": "不能為空",
            "invalid": "格式錯誤",
            "min_length": "用戶名最短8位"
        }
    )
    pwd = forms.CharField(
        min_length=6, 
        label="密碼",
        widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=https://www.cnblogs.com/mylu/p/True)
                                        #給input標簽添加class='c1',重繪頁面保留原input框中的值
        validators=[RegexValidator(r'^[0-9]+$', '請輸入數字'), RegexValidator(r'^159[0-9]+$', '數字必須以159開頭')],                       #自定義正則校驗器,如果不符合正則匹配則顯示'請輸入數字'
        validators=[passward_validate, ],#使用自定義驗證函式
    )                                   
    
    gender = forms.fields.ChoiceField(
        choices=((1, ""), (2, ""), (3, "保密")),
        label="性別",
        initial=3,
        widget=forms.widgets.RadioSelect()  #單radio值為字串
    )
    hobby = forms.fields.ChoiceField(       #單選Select
        choices=((1, "籃球"), (2, "足球"), (3, "雙色球"), ),
        label="愛好",
        initial=3,
        widget=forms.widgets.Select()
    )
    hobby = forms.fields.MultipleChoiceField(   #多選Select
        choices=((1, "籃球"), (2, "足球"), (3, "雙色球"), ),
        label="愛好",
        initial=[1, 3],
        widget=forms.widgets.SelectMultiple()
    )
    keep = forms.fields.ChoiceField(            #單選checkbox
        label="是否記住密碼",
        initial="checked",
        widget=forms.widgets.CheckboxInput()
    )
    hobby = forms.fields.MultipleChoiceField(   #多選checkbox
        choices=((1, "籃球"), (2, "足球"), (3, "雙色球"),),
        label="愛好",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )
  def __init__(self, *args, **kwargs):          #動態choices選項
        super(RegForm,self).__init__(*args, **kwargs)
        # self.fields['hobby'].choices = ((1, '籃球'), (2, '足球'),)
        self.fields['hobby'].choices = models.Hobby.objects.all().values_list('id','name')
?
# 使用form組件實作注冊方式
def register2(request):
    form_obj = RegForm()
    if request.method == "POST":
        form_obj = RegForm(request.POST)    # 實體化form物件的時候,把post提交過來的資料直接傳進去   
        if form_obj.is_valid():             # 呼叫form_obj校驗資料的方法
            #print(form_obj.cleaned_data)    # 獲取所有經過驗證的資料
            models.User.objects.create(**form_obj.cleaned_data)
            return HttpResponse("注冊成功")
    return render(request, "register.html", {"form_obj": form_obj})

register.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注冊2</title>
</head>
<body>
    <form action="/reg2/" method="post" novalidate autocomplete="off">
        {% csrf_token %}
        <div>
            <label for="{{ form_obj.username.id_for_label }}">
            {{ form_obj.username.label }}
            </label>
            {{ form_obj.username }} 
            <span class="error">{{ form_obj.username.errors.0 }}</span>
        </div>
        <div>
            <label for="{{ form_obj.pwd.id_for_label }}">
            {{ form_obj.pwd.label }}
            </label>
            {{ form_obj.pwd }} 
            <span class="error">{{ form_obj.pwd.errors.0 }}</span>
        </div>
        <div>
            <label for="">{{ form_obj.gender.label }}</label>
            {{ form_obj.gender }}
            <span class="error">{{ form_obj.gender.errors.0 }}</span>
        </div>
        <div>
            <label for="">{{ form_obj.hobby.label }}</label>
            {{ form_obj.hobby }}
            <span class="error">{{ form_obj.hobby.errors.0 }}</span>
        </div>
        <div>
            <label for="">{{ form_obj.keep.label }}</label>
            {{ form_obj.keep }}
            <span class="error">{{ form_obj.keep.errors.0 }}</span>
        </div>
        <div>
            <input type="submit" class="btn btn-success" value="注冊">
        </div>
    </form>
</body>
</html>

12.72 Hook方法

除了上面兩種方式,還可以在Form類中定義鉤子函式,來實作自定義的驗證功能

區域鉤子

在Form類中定義 clean_欄位名() 方法,就能夠實作對特定欄位進行校驗

class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用戶名",
        initial="張三",
        error_messages={
            "required": "不能為空",
            "invalid": "格式錯誤",
            "min_length": "用戶名最短8位"
        },
        widget=forms.widgets.TextInput(attrs={"class": "form-control"})
    )
    ...
    # 定義區域鉤子,用來校驗username欄位
    def clean_username(self):
        value = self.cleaned_data.get("username")
        if "666" in value:
            raise ValidationError("光喊666是不行的")
        else:
            return value

全域鉤子:

在Form類中定義 clean() 方法,就能夠實作對欄位進行全域校驗

class LoginForm(forms.Form):
    ...
    password = forms.CharField(
        min_length=6,
        label="密碼",
        widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=https://www.cnblogs.com/mylu/p/True)
    )
    re_password = forms.CharField(
        min_length=6,
        label="確認密碼",
        widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=https://www.cnblogs.com/mylu/p/True)
    )
    ...
    # 定義全域的鉤子,用來校驗密碼和確認密碼欄位是否相同
    def clean(self):
        password_value = self.cleaned_data.get('password')
        re_password_value = self.cleaned_data.get('re_password')
        if password_value =https://www.cnblogs.com/mylu/p/= re_password_value:
            return self.cleaned_data
        else:
            self.add_error('re_password', '兩次密碼不一致')
            raise ValidationError('兩次密碼不一致')

12.8 auth認證組件

urls.py:

from django.conf.urls import url
from django.contrib import admin
from app01 import views
?
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^login/', views.login),
    url(r'^register/', views.register),
    url(r'^home/', views.home),
    url(r'^logout/', views.logout),
    url(r'^set_password/', views.set_password),
]

views.py:

from django.shortcuts import render, redirect, HttpResponse
from django.contrib import auth
from django.contrib.auth.models import User
# from app01.models import UserInfo
from django.contrib.auth.decorators import login_required
?
def login(request):
    if request.method == "POST":
        username = request.POST.get("username")
        pwd = request.POST.get("pwd")
        # authenticate():校驗用戶名、密碼,認證成功回傳user物件
        user_obj = auth.authenticate(username=username, password=pwd)
        if user_obj:
            # 內置的login方法
            auth.login(request, user_obj)
            # 生成Session資料,存一下user_id 然后把sessionid寫入Cookie
            # 后續每一次請求來的時候,AuthenticationMiddleware中的process_request方法中
            # 會取到user_id,進而取到user物件,然后添加到request.user屬性中 --> request.user = user_obj
            # 后續可以通過request.user拿到當前的登陸用戶物件,否則得到一個匿名用戶物件
            return redirect("/home/")
    return render(request, "login.html")
?
def register(request):
    if request.method == "POST":
        username = request.POST.get("username")
        pwd = request.POST.get("pwd")
        # 呼叫內置的創建普通用戶的專用方法,創建一個新的普通用戶
        User.objects.create_user(username=username, password=pwd)
        #呼叫內置的創建超級用戶的專用方法,創建一個新的超級用戶
        #User.objects.create_superuser(username='用戶名',password='密碼')
    return render(request, "register.html")
?
@login_required     
#auth提供的裝飾器工具,用來快捷的給某個視圖添加登錄校驗,若用戶沒有登錄,則會跳轉到django默認的登錄URL '/accounts/login/ ' 并傳遞當前訪問url的絕對路徑 (登陸成功后,會重定向到該路徑),如果需要自定義登錄的URL,則需要在settings.py檔案中通過LOGIN_URL進行修改:LOGIN_URL = '/login/' 
def home(request):
    return render(request, "home.html")
?
# 呼叫auth內置的注銷方法
#當呼叫該函式時,當前請求的session資訊會全部清除,該用戶即使沒有登錄,使用該函式也不會報錯
def logout(request):
    auth.logout(request)
    return redirect("/login/")
?
@login_required
def set_password(request):
    if request.method == "POST":
        old_pwd = request.POST.get("old_pwd")
        pwd = request.POST.get("pwd")
        re_pwd = request.POST.get("re_pwd")
        user_obj = request.user
        if user_obj.check_password(old_pwd):# 先校驗原密碼是否正確,正確回傳True,否則回傳False
            if pwd == re_pwd:
                user_obj.set_password(pwd)   # 去資料庫修改密碼
                user_obj.save()             # 修改密碼一定要保存
                return redirect("/login/")
            elsereturn HttpResponse("兩次輸入不一致")
        else:
            return HttpResponse("原密碼錯誤")
    return render(request, "set_password.html")

login.html:

<body>
<h1>歡迎登陸</h1>
<form action="" method="post">
    {% csrf_token %}
    <input type="text" name="username">
    <input type="password" name="pwd">
    <input type="submit" value="登錄">
</form>
</body>

register.html:

<body>
<h1>歡迎注冊</h1>
<form action="/reg/" method="post">
    {% csrf_token %}
    <input type="text" name="username">
    <input type="password" name="pwd">
    <input type="submit" value="注冊">
</form>
</body>

home.html:

<body>
<h1>Hello,{{ request.user.username }}</h1>  #request.user獲取當前用戶物件
<a href="/logout/">注銷</a>
<a href="/set_password/">修改密碼</a>
</body>

set_password.html:

<body>
<form action="" method="post">
    {% csrf_token %}
    <p>原密碼:<input type="password" name="old_pwd"></p>
    <p>新密碼:<input type="password" name="pwd"></p>
    <p>重復密碼:<input type="password" name="re_pwd"></p>
    <p><input type="submit" value="修改"></p>
</form>
</body>

12.81 擴展默認的auth_user表

User類內沒有實作方法,只是繼承內置的 AbstractUser 類,通過繼承內置的 AbstractUser 類,來自定義一個Model類,這樣既能根據專案需求靈活的設計用戶表,又能使用Django強大的認證系統了

models:

from django.db import models
from django.contrib.auth.models import AbstractUser
?
class UserInfo(AbstractUser):
    phone = models.CharField(max_length=11)

注意:按上面的方式擴展了內置的auth_user表之后,一定要在settings.py中告訴Django,現在使用新定義的UserInfo表來做用戶認證

# 參考Django自帶的User表,繼承使用時需要設定
AUTH_USER_MODEL = "app01.UserInfo"

自定義認證系統默認使用的資料表之后,就可以像使用默認的auth_user表那樣使用自定義的UserInfo表了,比如:

創建普通用戶:

UserInfo.objects.create_user(username='用戶名', password='密碼')

創建超級用戶:

UserInfo.objects.create_superuser(username='用戶名', password='密碼')

注意:一旦指定了新的認證系統所使用的表,就需要重新在資料庫中創建該表,而不能繼續使用原來默認的auth_user表

12.9 自定義圖片驗證碼登錄

login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>歡迎登陸</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/mystyle.css">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-4 col-md-offset-4" id="login-form">
            <form autocomplete="off" novalidate>        {# 去掉瀏覽器驗證和提示 #}
                <div class="form-group">
                    <label for="{{ form_obj.username.id_for_label }}">
                    {{ form_obj.username.label }}</label>
                    {{ form_obj.username }}
                    {# <span class="error">{{ form_obj.username.errors.0 }}</span>    #}
                    {#ajax提交資料,此處不重繪#}
                </div>
?
                <div class="form-group">
                    <label for="{{ form_obj.password.id_for_label }}">
                    {{ form_obj.password.label }}</label>
                    {{ form_obj.password }}
                </div>
?
                <div class="form-group" id="v-code-wrapper">
                    <label for="{{ form_obj.password.id_for_label }}">驗證碼</label>
                    <input type="text" class="form-control" id="v-code-input">
                    <img src="/v_code/" alt="" id="v-code" >    //#此處請求URL
                </div>
?
                <button type="button" class="btn btn-success" id="login-button">登錄</button>
                <span class="error" id="login-error"></span>
            </form>
        </div>
    </div>
</div>
<script src="/static/jquery-3.3.1.min.js"></script>
<script src="/static/setupAjax.js"></script>        // #ajax請求,csrf_token驗證(跨站請求偽造)
<script>
    $(document).ready(function () {                 // #檔案加載完之后自動執行的
        $("#login-button").click(function () {       // #登錄按鈕點擊之后要做的事兒           
            $.ajax({
                url: "/login/",
                type: "POST",
                data: {
                    username: $("#id_username").val(),
                    password: $("#id_password").val(),
                    v_code: $("#v-code-input").val()
                },
                success: function (data) {
                    if (!data.code) {
                        location.href = data.data;
                    } else {
                        // #有錯誤
                        $("#login-error").text(data.data);
                    }
                },
                error: function (err) {
                    console.log(err)
                }
            })
        });
        // #當form中的input標簽獲取游標之后,就清空之前的錯誤資訊
        $("form input").focus(function () {
            $("#login-error").text("");
        });
?
        // #點擊圖片重繪驗證碼(如果是問號結尾就去掉問號,如果不是問號結尾就加個問號)
        $("#v-code").click(function () {
            var oUrl = this.src;  // #得到原始的url
            if (/[?]$/.test(oUrl)){
                this.src = "/v_code/";
            }else {
                this.src += "?";
            }
        });
    })
</script>
</body>
</html>

urls.py:

from django.conf.urls import url, include
from django.contrib import admin
from blog import views
from django.conf import settings
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^login/', views.login),
    url(r'^index/', views.index),
    url(r'^v_code/', views.v_code),
    url(r'^logout/', views.logout), 
]

views.py: (v_code:生成隨機圖片驗證碼函式)

from django.views.decorators.cache import never_cache
import random
?
@never_cache  # 指定該視圖不使用快取
def v_code(request):
    #匯入pillow模塊
    from PIL import Image, ImageDraw, ImageFont
   
    # 定義一個生成隨機顏色代碼的內部函式
    def get_color():
        return random.randint(0,255), random.randint(0,255), random.randint(0,255)
?
    # 生成一個圖片物件
    img_obj = Image.new(
        "RGB",
        (250, 35),
        color=get_color()
    )
?
    # 在圖片中加文字,生成一個畫筆物件
    draw_obj = ImageDraw.Draw(img_obj)
    # 加載字體檔案
    font_obj = ImageFont.truetype("static/font/kumo.ttf", size=28)
    # 寫字
    # draw_obj.text(
    #     (0, 0),  # 位置
    #     "A",  # 內容
    #     (0,0,0),  # 顏色
    #     font=font_obj
    # )
?
    # for回圈5次,每次寫一個隨機的字符
    tmp_list = []
    for i in range(5):
        n = str(random.randint(0, 9))     # 隨機生成一個數字
        l = chr(random.randint(97, 122))  # 隨機生成一個小寫的字母
        u = chr(random.randint(65, 90))   # 隨機生成一個大寫的字母
        r = random.choice([n, l, u])      # 從上面三個隨機選一個
        tmp_list.append(r)
        draw_obj.text(
            (i*48+20, 0),  # 位置
            r,            # 內容
            get_color(),   # 隨機顏色
            font=font_obj
        )
    # 得到生成的隨機驗證碼
    v_code_str = "".join(tmp_list)
    request.session["v_code"] = v_code_str.upper() #使用session保存驗證碼
    # global VCODE          不能使用全域變數保存驗證碼,會被覆寫,每一個請求應該對應自己的驗證碼
    # VCODE = v_code_str
?
        # 加干擾線
        # width = 250  # 圖片寬度(防止越界)
        # height = 35
        # for i in range(2):
        #     x1 = random.randint(0, width)
        #     x2 = random.randint(0, width)
        #     y1 = random.randint(0, height)
        #     y2 = random.randint(0, height)
        #     draw_obj.line((x1, y1, x2, y2), fill=get_color())
        #
        # 加干擾點
        # for i in range(2):
        #     draw_obj.point([random.randint(0, width), random.randint(0, height)], fill=get_color())
        #     x = random.randint(0, width)
        #     y = random.randint(0, height)
        #     draw_obj.arc((x, y, x+4, y+4), 0, 90, fill=get_color())
?
?
    # 第一版: 將生成的圖片保存到檔案中
    # with open("xx.png", "wb") as f:
    #     img_obj.save(f, "png")
    # print("圖片已經生成!")
    # with open("xx.png", "rb") as f:
    #     return HttpResponse(data, content_type="image/png")
?
    # 第二版:直接將圖片在記憶體中保存
    from io import BytesIO
    tmp = BytesIO()  # 生成一個io物件
    img_obj.save(tmp, "png")
    data = tmp.getvalue()
    return HttpResponse(data, content_type="image/png")

view.py:(login.py)

#前端顯示錯誤資訊的兩種方式:
#form表單提交資料,使用render重繪頁面,return render(request, "login.html", {"form_obj": form_obj}),
#前端頁面相應添加 <span >{{ form_obj.username.errors.0 }}</span>
#ajax提交資料區域重繪,使用js顯示錯誤資訊
from django.contrib import auth
def login(request):
    form_obj = forms.LoginForm()
    if request.method == "POST":
        ret = {"code": 0}
        username = request.POST.get("username")
        password = request.POST.get("password")
        v_code = request.POST.get("v_code", "")
?
        if v_code.upper() == request.session.get("v_code", ""):
            # 驗證碼正確
            user = auth.authenticate(username=username, password=password)
            if user:
                # 用戶名密碼正確
                auth.login(request, user)  # 生成session資料, session資料里保存user_id
                ret["data"] = "/index/"
            else:
                # 用戶名或密碼錯誤
                ret["code"] = 1
                ret["data"] = "用戶名或密碼錯誤"
        else:
            # 驗證碼錯誤
            ret["code"] = 1
            ret["data"] = "驗證碼錯誤"
        return JsonResponse(ret)
    return render(request, "login.html", {"form_obj": form_obj})

12.91 極驗科技滑動驗證碼

login2.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>歡迎登陸</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/mystyle.css">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-4 col-md-offset-4" id="login-form">
            <form autocomplete="off" novalidate>
                <div class="form-group">
                    <label for="{{ form_obj.username.id_for_label }}">
                    {{ form_obj.username.label }}</label>
                    {{ form_obj.username }}
                </div>
                <div class="form-group">
                    <label for="{{ form_obj.password.id_for_label }}">
                    {{ form_obj.password.label }}</label>
                    {{ form_obj.password }}
                </div>
                <button type="button" class="btn btn-success" id="login-button">登錄</button>
                <span class="error" id="login-error"></span>
                <div id="popup-captcha"></div>
            </form>
        </div>
    </div>
</div>
<script src="/static/jquery-3.3.1.min.js"></script>
<script src="/static/setupAjax.js"></script>                        #提供ajax的csrf_token驗證
<!-- 引入封裝了failback的介面--initGeetest -->
<script src="http://static.geetest.com/static/tools/gt.js"></script>//  #匯入js檔案
<script>
    var handlerPopup = function (captchaObj) {
        // #成功的回呼
        captchaObj.onSuccess(function () {
            var validate = captchaObj.getValidate();
            $.ajax({
                url: "/login2/", // #進行二次驗證
                type: "post",
                data: {
                    username: $("#id_username").val(),
                    password: $("#id_password").val(),
                    geetest_challenge: validate.geetest_challenge,
                    geetest_validate: validate.geetest_validate,
                    geetest_seccode: validate.geetest_seccode
                },
                success: function (data) {
                    if (!data.code) {
                        location.href = data.data;
                    } else {
                        // #有錯誤
                        $("#login-error").text(data.data);
                    }
                },
                error: function (err) {
                    console.log(err)
                }
            });
        });
         $("#login-button").click(function () {
            captchaObj.show();
        });
        // 將驗證碼加到id為captcha的元素里
        captchaObj.appendTo("#popup-captcha");
        // 更多介面參考:http://www.geetest.com/install/sections/idx-client-sdk.html
    };
?
    // #驗證開始需要向網站主后臺獲取id,challenge,success(是否啟用failback)
    $.ajax({
        url: "/pcgetcaptcha?t=" + (new Date()).getTime(), // 加亂數防止快取
        type: "get",
        dataType: "json",
        success: function (data) {
            // #使用initGeetest介面
            // #引數1:配置引數
            // #引數2:回呼,回呼的第一個引數驗證碼物件,之后可以使用它做appendTo之類的事件
            initGeetest({
                gt: data.gt,
                challenge: data.challenge,
                product: "popup", // #產品形式,包括:float,embed,popup,注意只對PC版驗證碼有效
                offline: !data.success // #表示用戶后臺檢測極驗服務器是否宕機,一般不需要關注
                // 更多配置引數請參見:http://www.geetest.com/install/sections/idx-client-sdk.html#config
            }, handlerPopup);
        }
    });
?
    $(document).ready(function () {          // #檔案加載完之后自動執行的                         
        $("form input").focus(function () {  // #當form中的input標簽獲取游標之后,就清空之前的錯誤資訊
            $("#login-error").text("");
        });
       
        $("#v-code").click(function () {      // #點擊圖片重繪驗證碼
            this.src += "?";
        });
    })
</script>
</body>
</html>

urls.py:

from django.conf.urls import url, include
from django.contrib import admin
from blog import views
from django.conf import settings
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/', views.index),
    url(r'^login2/', views.login2),
    # 極驗科技 獲取驗證碼的url
    url(r'^pcgetcaptcha/', views.pcgetcaptcha)
    url(r'^logout/', views.logout),
    # 以上都匹配不上,就由我來
    url(r'^$', views.index)
]

views.py:

# 極驗科技依賴
from geetest import GeetestLib
pc_geetest_id = "b46d1900d0a894591916ea94ea91bd2c"
pc_geetest_key = "36fc3fe98530eea08dfc6ce76e3d24c4"
?
# 滑動驗證碼加載需要的視圖函式
def pcgetcaptcha(request):
    user_id = 'test'
    gt = GeetestLib(pc_geetest_id, pc_geetest_key)
    status = gt.pre_process(user_id)
    request.session[gt.GT_STATUS_SESSION_KEY] = status
    request.session["user_id"] = user_id
    response_str = gt.get_response_str()
    return HttpResponse(response_str)
def login2(request):
    form_obj = forms.LoginForm()
    if request.method == "POST":
        gt = GeetestLib(pc_geetest_id, pc_geetest_key)
        challenge = request.POST.get(gt.FN_CHALLENGE, '')
        validate = request.POST.get(gt.FN_VALIDATE, '')
        seccode = request.POST.get(gt.FN_SECCODE, '')
        status = request.session[gt.GT_STATUS_SESSION_KEY]
        user_id = request.session["user_id"]
        if status:
            result = gt.success_validate(challenge, validate, seccode, user_id)
        else:
            result = gt.failback_validate(challenge, validate, seccode)
        # 如果驗證碼正確
        if result:
            ret = {"code": 0}
            username = request.POST.get("username")
            password = request.POST.get("password")
            user = auth.authenticate(username=username, password=password)
            if user:
                # 用戶名密碼正確
                ret["data"] = "/index/"
            else:
                # 用戶名或密碼錯誤
                ret["code"] = 1
                ret["data"] = "用戶名或密碼錯誤"
            return JsonResponse(ret)
    return render(request, "login2.html", {"form_obj": form_obj})

12.92 媒體檔案media、 kindeditor、beautifulsoup模塊

urls.py:

from django.conf.urls import url, include
from django.contrib import admin
from django.views.static import serve
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # media路由配置
    url(r'^media/(?P<path>.*)$', serve, {"document_root": settings.MEDIA_ROOT}),
]

settings.py:

# 用戶上傳的檔案配置項
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")

views.py:

# kindeditor 接收上傳檔案的視圖
from django.conf import settings
def upload_img(request):
    #print(request.POST)
    #print(request.FILES)
    # 把上傳的檔案保存在服務端
    file_obj = request.FILES.get('imgFile')
    with open(os.path.join(settings.MEDIA_ROOT, "article_img", file_obj.name), "wb") as f:
        # 從file_obj一點一點讀,往f里面一點一點寫
        for chunk in file_obj.chunks():
            f.write(chunk)
?
    # 回傳回應的格式要有要求
    ret = {"error": 0, "url": "/media/article_img/{}".format(file_obj.name)} #"error": 0表示回應成功
    return JsonResponse(ret)
?
# 添加新文章
def add_article(request):
    if request.method == "POST":
        print(request.POST)
        title = request.POST.get("title")
        content = request.POST.get("content")
        # 入庫之前要對資料做校驗
        from bs4 import BeautifulSoup       # 匯入beautifulsoup模塊,用于決議HTML標簽
        soup = BeautifulSoup(content, 'html.parser')
        for tag in soup.find_all("script"): 
            tag.decompose()                 # 找到每一個script標簽,并洗掉掉
        # 銷毀后的資料
        # 去資料庫創建新文章,操作兩張表:文章表和文章詳情表
        with transaction.atomic():
            article_obj = models.Article.objects.create(
                title=title,
                desc=soup.text[0:150],  # 針對檔案內容做截取,soup.text:去掉標簽后的文本
                user=request.user
            )
            models.ArticleDetail.objects.create(
                content=soup.prettify(),  # 經過處理后的文章內容,soup.prettify:格式化后的html
                article=article_obj
            )
        return redirect("/backend/")
    return render(request, "add_article.html")

add_article.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>添加新文章</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-12">
            <form action="/add_article/" method="post">
                {% csrf_token %}
                <div class="form-group">
                    <label for="article-title">標題</label>
                    <input type="text" name="title" class="form-control" id="article-title" placeholder="標題">
                </div>
                <div class="form-group">
                    <label for="article-content">內容</label>
                    <textarea id="article-content" name="content" class="form-control" cols="30" rows="10"></textarea>
                </div>
                <button type="submit" class="btn btn-success">發布</button>
            </form>
        </div>
    </div>
</div>
<script src="/static/jquery-3.3.1.min.js"></script> # 先匯入jquery.js
<script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
<script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script>
<script>
    KindEditor.ready(function (K) {
        window.editor = K.create('#article-content', {  // #設定textarea文本域
            width: '800px',             // #寬
            height: '500px',            // #高
            uploadJson: '/upload_img/',  // #上傳檔案的URL
            extraFileUploadParams: {csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()}  // 給上傳檔案添加csrftoken
        });
    });
</script>
</body>
</html>

12.10 自定義分頁

utils/mypage.py:(封裝)

class Page(object):
    """
    一個自定義分頁類,可以實作Django ORM資料的分頁展示
    使用說明:
        from utils import mypage
        page_obj = mypage.Page(total_num, current_page, 'publisher_list')
        publisher_list = data[page_obj.data_start:page_obj.data_end]
        page_html = page_obj.page_html()
?
        為了顯示效果,show_page_num最好使用奇數
    """
    def __init__(self, total_num, current_page, url_prefix, per_page=10, show_page_num=11):
        """
        :param total_num: 資料的總條數
        :param current_page: 當前訪問的頁碼
        :param url_prefix: 分頁代碼里a標簽的前綴
        :param per_page: 每一頁顯示多少條資料
        :param show_page_num: 頁面上最多顯示多少個頁碼
        """
        self.total_num = total_num
        self.url_prefix = url_prefix
        self.per_page = per_page
        self.show_page_num = show_page_num
        # 通過初始化傳入的值計算得到的值
        self.half_show_page_num = self.show_page_num // 2
        # 當前資料總共需要多少頁碼
        total_page, more = divmod(self.total_num, self.per_page)
        # 如果有余數,就把頁碼數+1
        if more:
            total_page += 1
        self.total_page = total_page
        # 對傳進來的當前頁碼數做有效性校驗
        try:
            current_page = int(current_page)
        except Exception as e:
            current_page = 1
        # 如果當前頁碼數大于總頁碼數,默認展示最后一頁的資料
        # current_page = total_page if current_page > total_page else current_page
        if current_page > self.total_page:
            current_page = self.total_page
        # 如果當前頁碼數小于1,默認展示第一頁的資料
        if current_page < 1:
            current_page = 1
?
        self.current_page = current_page
        # 求 頁面上 需要展示的頁碼范圍
        if self.current_page - self.half_show_page_num <= 1:
            page_start = 1
            page_end = show_page_num
        elif self.current_page + self.half_show_page_num >= self.total_page:
            page_end = self.total_page
            page_start = self.total_page - self.show_page_num + 1
        else:
            page_start = self.current_page - self.half_show_page_num
            page_end = self.current_page + self.half_show_page_num
?
        self.page_start = page_start
        self.page_end = page_end  # 我上面一通計算得到的頁面顯示的頁碼結束
        # 如果你一通計算的得到的頁碼數比我總共的頁碼數還多,我就把頁碼結束指定成我總共有的頁碼數
        if self.page_end > self.total_page:
            self.page_end = self.total_page
?
    @property
    def data_start(self):
        # 回傳當前頁應該從哪兒開始切資料
        return (self.current_page-1)*self.per_page
    @property
    def data_end(self):
        # 回傳當前頁應該切到哪里為止
        return self.current_page*self.per_page
?
    def page_html(self):
        li_list = []
        # 添加前面的nav和ul標簽
        li_list.append("""
            <nav aria-label="Page navigation">
            <ul >
        """)
        # 添加首頁
        li_list.append('<li><a href="https://www.cnblogs.com/{}/?page=1">首頁</a></li>'.format(self.url_prefix))
        # 添加上一頁
        if self.current_page <= 1:  # 沒有上一頁
            prev_html = '<li ><a aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>'
        else:
            prev_html = '<li><a href="https://www.cnblogs.com/{}/?page={}" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>'.format(self.url_prefix,
                self.current_page - 1)
        li_list.append(prev_html)
        for i in range(self.page_start, self.page_end + 1):
            if i == self.current_page:
                tmp = '<li ><a href="https://www.cnblogs.com/{0}/?page={1}">{1}</a></li>'.format(self.url_prefix, i)
            else:
                tmp = '<li><a href="https://www.cnblogs.com/{0}/?page={1}">{1}</a></li>'.format(self.url_prefix, i)
            li_list.append(tmp)
        # 添加下一頁
        if self.current_page >= self.total_page:  # 表示沒有下一頁
            next_html = '<li ><a aria-label="Previous"><span aria-hidden="true">&raquo;</span></a></li>'
        else:
            next_html = '<li><a href="https://www.cnblogs.com/{}/?page={}" aria-label="Previous"><span aria-hidden="true">&raquo;</span></a></li>'.format(
                self.url_prefix, self.current_page + 1)
        li_list.append(next_html)
        # 添加尾頁
        li_list.append('<li><a href="https://www.cnblogs.com/{}/?page={}">尾頁</a></li>'.format(self.url_prefix, self.total_page))
?
        # 添加nav和ul的結尾
        li_list.append("""
            </ul>
        </nav>
        """)
        # 將生成的li標簽 拼接成一個大的字串
        page_html = "".join(li_list)
        return page_html

urls.py:

from django.conf.urls import url
from blog import views
urlpatterns = [
    url(r'(.*)/(category|tag|archive)/(.*)/$', views.home),  # home(request, username, tag, "跆拳道")
    url(r'(.*)/$', views.home),  # home(request, username)
]

views.py:(使用)

def home(request, username, *args):
    print(username, args)
    print("=" * 120)
    """
    :param request: 請求物件
    :param username: 用戶名
    :return:
    """
    user_obj = models.UserInfo.objects.filter(username=username).first()
    # 去資料庫中根據用戶名找所有的文章
    if not user_obj:
        return HttpResponse("404")
    else:
        blog = user_obj.blog
?
    # # 查詢左側分類那些面板中用到的資料
    # # 1. 當前站點下的文章分類
    # # category_list = models.Category.objects.filter(blog=blog)
    # from django.db.models import Count
    # category_list = models.Category.objects.filter(blog=blog).annotate(num=Count("article")).values("title", "num")
    # # 2. 文章標簽分類
    # tag_list = models.Tag.objects.filter(blog=blog)
    # # 3. 日期歸檔
    # archive_list = models.Article.objects.filter(user=user_obj).extra(
    #     select={"ym": "DATE_FORMAT(create_time, '%%Y-%%m')"}
    # ).values("ym").annotate(num=Count("nid")).values("ym", "num")
    # print(archive_list)
?
?
    if not args: # 查當前用戶的所有文章
        data = https://www.cnblogs.com/mylu/p/models.Article.objects.filter(user__username=username)
    else:
        if args[0] == "category":  # 按照文章分類查詢
            data = https://www.cnblogs.com/mylu/p/models.Article.objects.filter(user=user_obj).filter(category__title=args[1])
        elif args[0] == "tag":      # 按照標簽查詢
            data = https://www.cnblogs.com/mylu/p/models.Article.objects.filter(user=user_obj).filter(tags__title=args[1])
        else:                      # 按照日期歸檔查詢
            year, month = args[1].split("-")
            data = models.Article.objects.filter(user=user_obj).filter(create_time__year=year, create_time__month=month)
    total_num = data.count()
    current_page = request.GET.get("page")
    url_prefix = request.path_info.strip("/")
?
    from utils import mypage
    page_obj = mypage.Page(total_num, current_page, url_prefix, per_page=1)
    data = data[page_obj.data_start:page_obj.data_end]
    page_html = page_obj.page_html()
?
    return render(request, "home.html", {
        "username": username,
        "blog": blog,
        "article_list": data,
        "page_html": page_html
    })

home.html:

{% extends 'base.html' %}
?
{% block page-main %}
    <div class="article-list">
        {% for article in article_list %}
            <div class="article">
                <div class="article-top">
                    <h2><a href="/blog/{{ username }}/article/{{ article.nid }}/">{{ article.title }}</a></h2>
                </div>
                <div class="article-middle">
                    <div class="media">
                        <div class="media-left media-middle">
                            <a href="#">
                                <img class="media-object article-avatar" src="/media/{{ article.user.avatar }}"
                                     alt="...">
                            </a>
                        </div>
                        <div class="media-body">
                            {{ article.desc }}
                        </div>
                    </div>
                </div>
                <div class="article-footer">
                    <span class="article-user"><a
                            href="/blog/{{ article.user.username }}/">{{ article.user.username }}</a></span>發布于
                    <span>{{ article.create_time }}</span>
                    <span class="glyphicon glyphicon-thumbs-up">點贊({{ article.up_count }})</span>
                    <span class="glyphicon glyphicon-comment">評論({{ article.comment_count }})</span>
                </div>
            </div>
            <hr>
        {% endfor %}
        {{ page_html|safe }} #page_html:所有的分頁tag
    </div>
{% endblock %}

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/173131.html

標籤:Python

上一篇:python 從PDF中提取附件

下一篇:Flask-資料庫操作

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more