自定义分页
1、目的&环境准备
目的把分页写成一个模块的方式然后在需要分页的地方直接调用模块就行了。
环境准备Django中生成一个APP并且注册,配置URL&Views
配置URL
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^user_list/',views.user_list), ]
注册APP
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01', ]
配置models
from __future__ import unicode_literals from django.db import models # Create your models here.class UserList(models.Model): username = models.CharField(max_length=32) age = models.IntegerField()
2、分析
分页在基本上行所有的大型网站中都是需要的,比如博客园的分页,当我们查询的时候或查看的时候他有很多博文,难道他是一下把所有的博文都给我们返回的吗?当然不是:如下图
我们可以根据提示来点击上一页和下一页或者点击我们想要查看的页面,然后显示我们要查看的博文链接!而不是一下把所有的博文给显示出来,这样即节省流量又能改善用户体验。
3、逐步完善分页代码
首先咱们先创建500条数据(之前不要忘记生成数据库),在第一访问时设置下就OK
3.1、配置views创建数据
#/usr/bin/env python #-*- coding:utf-8 -*-from django.shortcuts import render from django.shortcuts import HttpResponse import models # Create your views here.def user_list(request): for i in range(500): dic = {'username': 'name_%d' % i, 'age': i} models.UserList.objects.create(**dic) return HttpResponse('OK')
3.2、配置html显示数据并通过views获取数据显示
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><table> {% for line in result %} <tr><td>{{ line.username }}</td><td>{{ line.age }}</td></tr> {% endfor %} </table><div> {{ pager_str|safe }} </div></body></html>
views
def user_list(request): result = models.UserList.objects.all() return render(request,'user_list.html',{'result':result})
这样,前端可以正常显示咱们去的数据了,但是一下子把所有的数据都取出来了。不是咱们想要的结果。
3.3、取指定的条数
比如我想取前10条怎么取呢?
def user_list(request): result = models.UserList.objects.all()[0:10] return render(request,'user_list.html',{'result':result})
那我让[0:10]动态起来就可以了!开始和结尾。
3.4、每页显示10条数据
向用户获取页数,我们可以配置URL在URL中有两种方式查询直接在URL中配置正则,然后在views里可以通过参数来获取用户传过来的值,
也可以通过?a=9 &b=10 这样在Veiws函数里可以通过request.GET['a']取值了,并且可以设置默认值request.GET.get('a',0)
def user_list(request): current_page = request.GET.get('page') print current_page result = models.UserList.objects.all()[0:10] return render(request,'user_list.html',{'result':result})
测试:
这里的问号是通过get的方式向后端发送数据,我们也可以修改form表单中method改为GET,那么数据访问的时候就是通过GET方式向后端提交数据的,当点击form表单提交数据的时候,form里的内容会自动添加到浏览器的URL中以上面的格式!
虽然POST和GET都可以向后端提交数据但是规定:GET一般用来查询使用,POST一般用来提交修改数据使用,根据实际的情况来定!
看下面的代码:
def user_list(request): current_page = request.GET.get('page',1) print current_page start = 0 #10 20 (current_page-1)*10 end = 10 #20 30 current_page*10 result = models.UserList.objects.all()[start:end] return render(request,'user_list.html',{'result':result})
我们现在让start & end 动态起来页面动态起来是不是就更好了?
def user_list(request): current_page = request.GET.get('page',1) current_page = int(current_page) start = (current_page-1)*10 #10 20 (current_page-1)*10 end = current_page*10 #20 30 current_page*10 result = models.UserList.objects.all()[start:end] return render(request,'user_list.html',{'result':result})
然后打开网页测试下,修改page的值来看下显示效果:
http://127.0.0.1:8000/user_list/?page=2
http://127.0.0.1:8000/user_list/?page=3
http://127.0.0.1:8000/user_list/?page=4
分页代码整理
我们通过把上面的代码整理一下整理到函数或类里下次使用的时候直接调用类或者函数即可。
#/usr/bin/env python #-*- coding:utf-8 -*-from django.shortcuts import render from django.shortcuts import HttpResponse import models # Create your views here.class Pager(object): def __init__(self,current_page): self.current_page = int(current_page) #把方法伪造成属性(1) @property def start(self): return (self.current_page-1)*10 @property def end(self): return self.current_page*10 def user_list(request): current_page = request.GET.get('page',1) page_obj = Pager(current_page) #把方法改造成属性(2),这样在下面调用方法的时候就不需要加括号了 result = models.UserList.objects.all()[page_obj.start:page_obj.end] return render(request,'user_list.html',{'result':result})
2、生成按钮页
上面的功能实现了分页现在,我们是不是可以在下面生成一堆的按钮来让用户点击,而不是在浏览器中输入!
先看下如果咱们手动写的话!
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><table> {% for line in result %} <tr><td>{{ line.username }}</td><td>{{ line.age }}</td></tr> {% endfor %} </table><div><a href="/user_list/?page=1">1</a><a href="/user_list/?page=2">2</a><a href="/user_list/?page=3">3</a><a href="/user_list/?page=4">4</a><a href="/user_list/?page=5">5</a></div></body></html>
如下图:
目的是为了实现左侧的效果,但是咱们不可能手写吧?所以应该在后台创建,这个是不是就是总共有多少页?
需求分析:
每页显示10条数据
共500条数据就是50页,那有501条数据呢?是多少页是51页。
可以通过divmod(500,10),他会的到一个元组,第一个值是值,第二个值是余数,我们就通过判断他是否有余数来判断是否是整页,如果有余数在整页基础加1即可!
我们获取了所有的页数之后可以吧他返回在前端生成,但是这样我们后端做了分页之后前端是不是也做了分页操作?
我们可以在后端生成一个大字符串然后把他返回给前端(拼接的URL)
#/usr/bin/env python #-*- coding:utf-8 -*-from django.shortcuts import render from django.shortcuts import HttpResponse import models # Create your views here.class Pager(object): def __init__(self,current_page): self.current_page = int(current_page) #把方法伪造成属性(1) @property def start(self): return (self.current_page-1)*10 @property def end(self): return self.current_page*10 def user_list(request): current_page = request.GET.get('page',1) page_obj = Pager(current_page) #吧方法未造成属性(2),这样在下面调用方法的时候就不需要加括号了 result = models.UserList.objects.all()[page_obj.start:page_obj.end] all_item = models.UserList.objects.all().count() all_page,div = divmod(all_item,10) if div > 0: all_page +=1 pager_str = ""for i in range(1,all_page+1): #每次循环生成一个标签 temp = '<a href="/user_list/?page=%d">%d</a>' %(i,i,) #把标签拼接然后返回给前端 pager_str += temp return render(request,'user_list.html',{'result':result,'pager_str':pager_str})
前端配置:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><table> {% for line in result %} <tr><td>{{ line.username }}</td><td>{{ line.age }}</td></tr> {% endfor %} </table><div> {{ pager_str }} </div></body></html>
但是有个问题如下图:
这个是什么问题导致的呢?
咱们在之前的文章学过一个CSRF跨站请求伪造的安全设置,这里还有一个值得说的安全就是XSS攻击:
XSS攻击:跨站脚本攻击(Cross Site Scripting),为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。
XSS是一种经常出现在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。比如这些代码包括HTML代码和客户端脚本。攻击者利用XSS漏洞旁路掉访问控制——例如同源策略(same origin policy)。这种类型的漏洞由于被黑客用来编写危害性更大的网络钓鱼(Phishing)攻击而变得广为人知。对于跨站脚本攻击,黑客界共识是:跨站脚本攻击是新型的“缓冲区溢出攻击“,而JavaScript是新型的“ShellCode”。
咱们上面看到的内容就是Django为了防止这样的情况产生而设置的机制,例子如下:
如果,写的简单的script可以被保存为HTML标签的话那么?只要有人打开这个页面都会提示一个信息,这样是不是不安全,这个是小的说的!
如果说这里是一个获取Cookie然后发送到指定服务器然后服务器可以通过cookie访问你的个人信息是不是就可怕了?!
那么怎么解决这个问题呢?可以通过模板语言的一个函数来告诉Django这个是安全的:加一个safe (在文章的后面还有一种方法在后端标记他是安全的)
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><table> {% for line in result %} <tr><td>{{ line.username }}</td><td>{{ line.age }}</td></tr> {% endfor %} </table><div> {{ pager_str|safe }} </div></body></html>
效果如下:
这样全显示出来了,不是很好,最好显示11页然后,当用户点击第6页的时候,这个6永远在中间!所以我们也要让页码动态起来!
#/usr/bin/env python #-*- coding:utf-8 -*-from django.shortcuts import render from django.shortcuts import HttpResponse import models # Create your views here.class Pager(object): def __init__(self,current_page): self.current_page = int(current_page) #把方法伪造成属性(1) @property def start(self): return (self.current_page-1)*10 @property def end(self): return self.current_page*10 def page_str(self,all_item,base_url): all_page, div = divmod(all_item, 10) if div > 0: all_page += 1 pager_str = ""#默认可以看到的页码11个 start = self.current_page - 5 end = self.current_page + 6 #把页面动态起来传入起始和结束for i in range(start, end): #判断是否为当前页if i == self.current_page: temp = '<a style="color:red;font-size:26px;padding: 5px" href="%s?page=%d">%d</a>' % (base_url,i,i) else: temp = '<a style="padding: 5px" href="%s?page=%d">%d</a>' % (base_url,i,i) # 把标签拼接然后返回给前端 pager_str += temp return pager_str def user_list(request): current_page = request.GET.get('page',1) page_obj = Pager(current_page) #吧方法未造成属性(2),这样在下面调用方法的时候就不需要加括号了 result = models.UserList.objects.all()[page_obj.start:page_obj.end] all_item = models.UserList.objects.all().count() pager_str = page_obj.page_str(all_item,'/user_list/') return render(request,'user_list.html',{'result':result,'pager_str':pager_str})
前端页面不用修改了,看效果如下:
现在还是有问题的,当你现在点击1,2,3,4,5的时候因为前面没有页面就会出现负值!
并且最后一页页会出现无穷大,但是没有数据,那怎么解决呢?
''' 如果总页数 <= 11 比如9页: 那么,还有必要让页码动起来吗?就没必要了直接显示总共页数就行了!start就是1,end就是9就行了 else: 如果当前页小宇6: start:1 end:11 如果当前页大于6: start:当前页-5 end:当前页+6 如果当前页+6 >总页数: start:总页数-10 end:总页数 '''
修改后的代码:
#/usr/bin/env python #-*- coding:utf-8 -*-from django.shortcuts import render from django.shortcuts import HttpResponse import models # Create your views here.class Pager(object): def __init__(self,current_page): self.current_page = int(current_page) #把方法伪造成属性(1) @property def start(self): return (self.current_page-1)*10 @property def end(self): return self.current_page*10 def page_str(self,all_item,base_url): all_page, div = divmod(all_item, 10) if div > 0: all_page += 1 pager_str = ""# #默认可以看到的页码11个# # start = self.current_page - 5# end = self.current_page + 6# # #把页面动态起来传入起始和结束# for i in range(start, end):# # #判断是否为当前页# if i == self.current_page:# temp = '<a style="color:red;font-size:26px;padding: 5px" href="%s?page=%d">%d</a>' % (base_url,i,i)# else:# temp = '<a style="padding: 5px" href="%s?page=%d">%d</a>' % (base_url,i,i)# # # 把标签拼接然后返回给前端# pager_str += tempif all_page <= 11: start = 1 end = all_page else: if self.current_page <= 6: start = 1 end = 11 + 1 else: start = self.current_page - 5 end = self.current_page + 6 if self.current_page + 6 > all_page: start = all_page - 10 end = all_page + 1 #把页面动态起来传入起始和结束for i in range(start, end): #判断是否为当前页if i == self.current_page: temp = '<a style="color:red;font-size:26px;padding: 5px" href="%s?page=%d">%d</a>' % (base_url,i,i) else: temp = '<a style="padding: 5px" href="%s?page=%d">%d</a>' % (base_url,i,i) # 把标签拼接然后返回给前端 pager_str += temp return pager_str def user_list(request): current_page = request.GET.get('page',1) page_obj = Pager(current_page) #把方法改造成属性(2),这样在下面调用方法的时候就不需要加括号了 result = models.UserList.objects.all()[page_obj.start:page_obj.end] all_item = models.UserList.objects.all().count() pager_str = page_obj.page_str(all_item,'/user_list/') return render(request,'user_list.html',{'result':result,'pager_str':pager_str})
这样简单分页就完成了,现在我们可以在给他增加一个“上一页” & 下一页
我们可以把他给为一个列表,然后每次生成的标签append到列表中,然后分别在列表中最前面和后面增加,上一页和下一页
#/usr/bin/env python #-*- coding:utf-8 -*-from django.shortcuts import render from django.shortcuts import HttpResponse import models # Create your views here.class Pager(object): def __init__(self,current_page): self.current_page = int(current_page) #把方法伪造成属性(1) @property def start(self): return (self.current_page-1)*10 @property def end(self): return self.current_page*10 def page_str(self,all_item,base_url): all_page, div = divmod(all_item, 10) if div > 0: all_page += 1 pager_list = [] if all_page <= 11: start = 1 end = all_page else: if self.current_page <= 6: start = 1 end = 11 + 1 else: start = self.current_page - 5 end = self.current_page + 6 if self.current_page + 6 > all_page: start = all_page - 10 end = all_page + 1 #把页面动态起来传入起始和结束for i in range(start, end): #判断是否为当前页if i == self.current_page: temp = '<a style="color:red;font-size:26px;padding: 5px" href="%s?page=%d">%d</a>' % (base_url,i,i) else: temp = '<a style="padding: 5px" href="%s?page=%d">%d</a>' % (base_url,i,i) # 把标签拼接然后返回给前端 pager_list.append(temp) #上一页if self.current_page > 1: pre_page = '<a href="%s?page=%d">上一页</a>' % (base_url, self.current_page - 1) else: # javascript:void(0) 什么都不干 pre_page = '<a href="javascript:void(0);">上一页</a>'#下一页if self.current_page >= all_page: next_page = '<a href="javascript:void(0);">下一页</a>'else: next_page = '<a href="%s?page=%d">下一页</a>' % (base_url, self.current_page + 1) pager_list.insert(0, pre_page) pager_list.append(next_page) return "".join(pager_list) def user_list(request): current_page = request.GET.get('page',1) page_obj = Pager(current_page) #把方法改造成属性(2),这样在下面调用方法的时候就不需要加括号了 result = models.UserList.objects.all()[page_obj.start:page_obj.end] all_item = models.UserList.objects.all().count() pager_str = page_obj.page_str(all_item,'/user_list/') return render(request,'user_list.html',{'result':result,'pager_str':pager_str})
最后写成模块,也可以吧分页的数量动态化!
前面说过关于XSS的问题,还有一种方式在后端告诉他是安全的即可,前端就不需要标记了。
导入并使用模块即可:
#!/usr/bin/env python #-*- coding:utf-8 -*-from django.utils.safestring import mark_safe #把拼接的HTML标记为安全的即可return mark_safe("".join(pager_list))
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><table> {% for line in result %} <tr><td>{{ line.username }}</td><td>{{ line.age }}</td></tr> {% endfor %} </table><div> {{ pager_str }} </div></body></html>
#!/usr/bin/env python #-*- coding:utf-8 -*-from django.utils.safestring import mark_safe class Pager(object): def __init__(self,current_page): self.current_page = int(current_page) #把方法伪造成属性(1) @property def start(self): return (self.current_page-1)*10 @property def end(self): return self.current_page*10 def page_str(self,all_item,base_url): all_page, div = divmod(all_item, 10) if div > 0: all_page += 1 pager_list = [] if all_page <= 11: start = 1 end = all_page else: if self.current_page <= 6: start = 1 end = 11 + 1 else: start = self.current_page - 5 end = self.current_page + 6 if self.current_page + 6 > all_page: start = all_page - 10 end = all_page + 1 #把页面动态起来传入起始和结束for i in range(start, end): #判断是否为当前页if i == self.current_page: temp = '<a style="color:red;font-size:26px;padding: 5px" href="%s?page=%d">%d</a>' % (base_url,i,i) else: temp = '<a style="padding: 5px" href="%s?page=%d">%d</a>' % (base_url,i,i) # 把标签拼接然后返回给前端 pager_list.append(temp) #上一页if self.current_page > 1: pre_page = '<a href="%s?page=%d">上一页</a>' % (base_url, self.current_page - 1) else: # javascript:void(0) 什么都不干 pre_page = '<a href="javascript:void(0);">上一页</a>'#下一页if self.current_page >= all_page: next_page = '<a href="javascript:void(0);">下一页</a>'else: next_page = '<a href="%s?page=%d">下一页</a>' % (base_url, self.current_page + 1) pager_list.insert(0, pre_page) pager_list.append(next_page) return mark_safe("".join(pager_list))