Form组件、auth认证组件、自定义图片验证码登录、 自定义分页
12.7 Form组件
Django form组件实现的功能:生成页面可用的HTML标签、对用户提交的数据进行校验、保留上次输入内容
12.71 使用form组件实现注册功能
# 按照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=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=True) ) re_password = forms.CharField( min_length=6, label="确认密码", widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True) ) ... # 定义全局的钩子,用来校验密码和确认密码字段是否相同 def clean(self): password_value = self.cleaned_data.get('password') re_password_value = self.cleaned_data.get('re_password') if password_value == 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 class="error">{{ 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 class="pagination"> """) # 添加首页 li_list.append('<li><a href="/{}/?page=1">首页</a></li>'.format(self.url_prefix)) # 添加上一页 if self.current_page <= 1: # 没有上一页 prev_html = '<li class="disabled"><a aria-label="Previous"><span aria-hidden="true">«</span></a></li>' else: prev_html = '<li><a href="/{}/?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 class="active"><a href="/{0}/?page={1}">{1}</a></li>'.format(self.url_prefix, i) else: tmp = '<li><a href="/{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 class="disabled"><a aria-label="Previous"><span aria-hidden="true">»</span></a></li>' else: next_html = '<li><a href="/{}/?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="/{}/?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 = models.Article.objects.filter(user__username=username) else: if args[0] == "category": # 按照文章分类查询 data = models.Article.objects.filter(user=user_obj).filter(category__title=args[1]) elif args[0] == "tag": # 按照标签查询 data = 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 %}