Django开发笔记(十)Django组件(下)
四、Django分页器
from django.core.paginator import Paginator
分页案例:
views.py:
from django.shortcuts import render,HttpResponse
from page.models import Book
from django.core.paginator import Paginator,EmptyPage
# Create your views here.
def multi_create(request):
# Book.objects.create(title=,price=)
# 用for循环不好,会循环insert into () values () 找表插值n多次,很影响性能
book_list = []
for i in range(1,1000):
book = Book(title="书籍"+str(i),price=i*1.2)
book_list.append(book)
Book.objects.bulk_create(book_list)
return HttpResponse("批量导入成功")
def index(request):
booklist = Book.objects.all()
# 分页对象
paginator = Paginator(booklist,per_page=10) # per_page每页条数
# # 1.分页信息
# print(paginator.count) # 1000
# print(paginator.num_pages) # 分页数 100
# print(paginator.page_range) # 分页列表
# 2.获取某页对象
# page01 = paginator.page(3) # 这样写死了的方式不好
try:
visit_page_num = int(request.GET.get("page",1)) # request参数page:http://127.0.0.1:8000/page/books/?page=10
page = paginator.page(visit_page_num)
except EmptyPage :
page = paginator.page(1) # 默认展示第一页
# # page01 = paginator.page(3)
# # 1)获取该页的所有数据
# # 方式1
# print(page01.object_list)
# # 方式2
# for book in page01:
# print(book)
# # 2)页对象的其他属性
# page01.next_page_number() # 拿到上一页或下一页的对象number
# page01.previous_page_number()
# page01.has_next() # 判断有没有上一页或下一页
# page01.has_previous()
# page_book_list = page.object_list # 这种写法并不友好,一下子把所有东西拿过去
# return render(request,"page/index.html",{"book_list":page_book_list})
# 可以直接使用page渲染,效果和上面两行一样
return render(request,
"page/index.html", {"book_list": page,"paginator":paginator,"visit_page_num":visit_page_num})
index.html:(重点)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
<!-- 引入 Bootstrap -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<!-- jQuery (Bootstrap 的 JavaScript 插件需要引入 jQuery) -->
<script src="https://code.jquery.com/jquery.js"></script>
<!-- 包括所有已编译的插件 -->
<script src="js/bootstrap.min.js"></script>
</head>
<body>
<ul>
{% for book in book_list %}
<li> {{ book.title }} : {{ book.price }}</li>
{% endfor %}
</ul>
<ul class="pagination">
{% if book_list.has_previous %}
<li><a href="/page/books?page={{ book_list.previous_page_number }}" aria_lable="Previous">
<span aria_hidden="true">上一页</span></a></li>
{% else %}
<li class="disabled"><a href="#">
<span aria_hidden="true">上一页</span></a></li>
{% endif %}
</a></li>
{% for foo in paginator.page_range %}
{% if visit_page_num == foo %}
<li class="active"><a href="/page/books?page={{ foo }}">{{ foo }}</a></li>
{% else %}
<li><a href="/page/books?page={{ foo }}">{{ foo }}</a></li>
{% endif %}
{% endfor %}
{% if book_list.has_next %}
<li><a href="/page/books?page={{ book_list.next_page_number }}" aria_lable="Previous">
<span aria_hidden="true">下一页</span></a></li>
{% else %}
<li class="disabled"><a href="#">
<span aria_hidden="true">下一页</span></a></li>
{% endif %}
</ul>
</body>
</html>
代码优化:
如果页码非常多,我想做一个中间页码用...省略号代替的功能,只显示当前页码的前一页后一页。和博客园这个页码功能一样这样的展示。
但是我这个运行得特别慢,不道为什么捏。
views.py:
# 这个判断可以在视图函数里做,也可以在模板里做,没有什么区别
if visit_page_num == 1:
page_range = [visit_page_num, visit_page_num + 1]
elif visit_page_num == paginator.num_pages:
page_range = [visit_page_num - 1, visit_page_num]
else:
page_range = [visit_page_num - 1, visit_page_num, visit_page_num + 1]
return render(request,
"page/index.html", {"book_list": page, "paginator": paginator,
"visit_page_num": visit_page_num, "page_range": page_range})
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
<!-- 新 Bootstrap 核心 CSS 文件 -->
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<ul>
{% for book in book_list %}
<li> {{ book.title }} : {{ book.price }}</li>
{% endfor %}
</ul>
<nav aria_lable="Page navigation">
<ul class="pagination">
<li>
<a href="/page/books?page=1" aria_lable="Next">
<span aria_hidden="true">第一页</span>
</a>
</li>
<li>
<a href="#">
<span aria_hidden="true">...</span>
</a>
</li>
{# page_range是我们在视图定义的一个变量:page_range = [visit_page_num - 1, visit_page_num, visit_page_num + 1]#}
{% for foo in page_range %}
{% if foo == visit_page_num %}
<li class="active"><a href="/page/books?page={{ foo }}">{{ foo }}</a></li>
{% else %}
<li><a href="/page/books?page={{ foo }}">{{ foo }}</a></li>
{% endif %}
{% endfor %}
<li>
<a href="#">
<span aria_hidden="true">...</span>
</a>
</li>
<li>
<a href="/page/books?page={{ paginator.num_pages }}" aria_lable="Previous">
<span aria_hidden="true">最后一页</span>
</a>
</li>
</ul>
</nav>
</body>
</html>
最终效果:

五、FBV与CBV
FBV :function based view 基于函数实现的视图逻辑
BCV:class based view 基于类实现的视图逻辑,能够将python中面向对象的知识嵌入到Django体系下,可以在类的框架下更好的表达逻辑
1.前后端分离模式
1)前后端不分离:[客户端看到的内容和所有界面效果都是由服务端提供出来的。]

2)前后端分离:[把前端的界面效果(html,css,js分离到另一个服务端,python服务端只需要返回数据即可)] 前端形成一个独立的网站,服务端构成一个独立的网站
其实是一种职责分离。前后端分离,那么你的模板语法再也用不上了,也就是render函数,再也不会用了,取而代之的就是返回一个json字符串。
此过程中,前端页面的设计和构建不再由后端开发人员构建了。

2.api接口
应用程序编程接口(Application Programming Interface,API接口),就是应用程序对外提供了一个操作数据的入口,这个入口可以是一个函数或类方法,也可以是一个url地址或者一个网络地址。当客户端调用这个入口,应用程序则会执行对应代码操作,给客户端完成相对应的功能。
当然,api接口在工作中是比较常见的开发内容,有时候,我们会调用其他人编写的api接口,有时候,我们也需要提供api接口给其他人操作。由此就会带来一个问题,api接口往往都是一个函数、类方法、或者url或其他网络地址,不断是哪一种,当api接口编写过程中,我们都要考虑一个问题就是这个接口应该怎么编写?接口怎么写的更加容易维护和清晰,这就需要大家在调用或者编写api接口的时候要有一个明确的编写规范!!!
为了在团队内部形成共识、防止个人习惯差异引起的混乱,我们都需要找到一种大家都觉得很好的接口实现规范,而且这种规范能够让后端写的接口,用途一目了然,减少客户端和服务端双方之间的合作成本。
目前市面上大部分公司开发人员使用的接口实现规范主要有:restful、RPC。
1)RPC( Remote Procedure Call ):
翻译成中文:远程过程调用[远程服务调用]. 从字面上理解就是访问/调用远程服务端提供的api接口。这种接口一般以服务或者过程式代码提供。
-
服务端提供一个唯一的访问入口地址:http://api.xxx.com/ 或 http://www.xx.com/api 或者基于其他协议的地址
-
客户端请求服务端的时候,所有的操作都理解为动作(action),一般web开发时,对应的就是HTTP请求的post请求
-
通过请求体参数,指定要调用的接口名称和接口所需的参数
action=get_all_student&class=301&sex=1
m=get_all_student&sex=1&age=22
command=100&sex=1&age=22
rpc接口多了,对应函数名和参数就多了,前端在请求api接口时难找.对于年代久远的rpc服务端的代码也容易出现重复的接口
2)restful: 翻译成中文: 资源状态转换.(表征性状态转移)(重点)
-
把服务端提供的所有的数据/文件都看成资源, 那么通过api接口请求数据的操作,本质上来说就是对资源的操作了.
因此,restful中要求,我们把当前接口对外提供哪种资源进行操作,就把资源的名称写在url地址。
-
web开发中操作资源,最常见的最通用的无非就是增删查改,所以restful要求在地址栏中声明要操作的资源是什么。然后通过http请求动词来说明对该资源进行哪一种操作.
POST http://www.xxx.com/api/students/ 添加学生数据
GET http://www.xxx.com/api/students/ 获取所有学生
GET http://www.xxx.com/api/students/1/ 获取id=pk的学生
DELETE http://www.xxx.com/api/students/1/ 删除id=pk的一个学生
PUT http://www.xxx.com/api/students/1/ 修改一个学生的全部信息 [id,name,sex,age,]
PATCH http://www.xxx.com/api/students/1/ 修改一个学生的部分信息[age]
也就是说,我们仅需要通过url地址上的资源名称结合HTTP请求动作,就可以说明当前api接口的功能是什么了。restful是以资源为主的api接口规范,体现在地址上就是资源就是以名词表达。rpc则以动作为主的api接口规范,体现在接口名称上往往附带操作数据的动作。
3.RESTful API概览
REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。 它首次出现在2000年Roy Fielding的博士论文中。
RESTful是一种专门为Web 开发而定义API接口的设计风格,尤其适用于前后端分离的应用模式中。
这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源。
而对于数据资源分别使用POST、DELETE、GET、UPDATE等请求动作来表达对数据的增删查改。
| GET | /students | 获取所有学生 |
|---|---|---|
| 请求方法 | 请求地址 | 后端操作 |
| POST | /students | 增加学生 |
| GET | /students/ | 获取编号为pk的学生 |
| PUT | /students/ | 修改编号为pk的学生 |
| DELETE | /students/ | 删除编号为pk的学生 |
restful规范是一种通用的规范,不限制语言和开发框架的使用。事实上,我们可以使用任何一门语言,任何一个框架都可以实现符合restful规范的API接口。
参考文档:http://www.runoob.com/w3cnote/restful-architecture.html
接口实现过程中,会存在幂等性。所谓幂等性是指代客户端发起多次同样请求时,是否对于服务端里面的资源产生不同结果。如果多次请求,服务端结果还是一样,则属于幂等接口,如果多次请求,服务端产生结果是不一样的,则属于非幂等接口。
| 请求方式 | 是否幂等 | 是否安全 |
|---|---|---|
| GET | 幂等 | 安全 |
| POST | 不幂等 | 不安全 |
| PUT/PATCH | 幂等 | 不安全 |
| DELETE | 幂等 | 不安全 |
4.CBV使用
之前我们用的视图函数叫FBV(也就是函数型视图函数),这里我们来试试CBV(类视图函数)的写法。类视图函数可以让代码看起来更简洁,用起来更方便。
urls.py:
path('index/',views.IndexView.as_view()),
views.py:
from django.shortcuts import render,HttpResponse
from django.views import View
from app01.models import Book
# Create your views here.
# # FBV模式
# def index(request):
# if request.method == "GET":
#
# return HttpResponse("GET")
# elif request.method == "POST":
#
# return HttpResponse("POST")
#
# elif request.method == "DELETE":
# return HttpResponse("DELETE")
# CBV模式 类视图名字一般后面带个View
# 基于restful开发
class IndexView(View):
def get(self,request):
return HttpResponse("CBV GET")
def post(self,request):
return HttpResponse("CBV POST")
def delete(self,request):
return HttpResponse("CBV DELETE")
class BookView(View):
def get(self,request):
# 获取数据
book_list = Book.objects.all()
# 序列化,jsonresponse什么的是针对python数据进行序列化的,比如列表啊字典啊,但是我们现在得到的是querySet
# querySet是Django的数据类型,有专门的序列化函数
from django.core import serializers
book_json = serializers.serialize("json",book_list)
# content_type可选参数
return HttpResponse(book_json,content_type="json")
5.CBV源码解析:
CBV的本质依然是FBV。
读源码小窍门:读不懂就不读咯。一些特殊情况不用管,直接看核心内容。
六、csrftoken(跨站请求伪造)
之前我们遇到过这个。
CSRF(Cross-Site Request Forgery,跨站点伪造请求)是一种网络攻击方式,该攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在未授权的情况下执行在权限保护之下的操作,具有很大的危害性。具体来讲,可以这样理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。
-- 企业邮箱
-- pornhub.com
token其实就是一个令牌,用于用户验证的,token的诞生离不开CSRF。正是由于上面的Cookie/Session的状态保持方式会出现CSRF,所以才有了token。
- 解除中间件注释

- 无csrf_token数据的post请求

补充知识点:
前端页面 {% csrf_token %}自动生成token
还有一个<input type="hidden"> 这个type除了text和password还可以是hidden,隐藏这个键值对。
1.基本使用
1)form表单提交
在html页面form表单中直接添加{% csrf_token%}

2)ajax提交
前后端分离就不能用上面的那种方式啦。需要后端将token给前端js代码。
这边代码待补充,视频是编号90。也没看,头疼,不愿意看。
2.实现原理
p91
浙公网安备 33010602011771号