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/") else: return 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">«</span></a></li>' else: prev_html = '<li><a href="https://www.cnblogs.com/{}/?page={}" aria-label="Previous"><span aria-hidden="true">«</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">»</span></a></li>' else: next_html = '<li><a href="https://www.cnblogs.com/{}/?page={}" aria-label="Previous"><span aria-hidden="true">»</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-資料庫操作
