编程是数据结构和算法的结合,而在Web类型的App中,咱们对于数据的操做请求是经过url来承载的,本文详细介绍了REST规范和CBV请求流程。css
编程是数据结构和算法的结合,小程序如简单的计算器,咱们输入初始数据,通过计算,获得最终的数据,这个过程当中,初始数据和结果数据都是数据,而计算过程是咱们所说的广义上的算法。html
大程序,如一个智能扫地机器人,咱们能够设置打扫的距离,左右摆动的幅度来打扫房间,这里面打扫的举例,摆动幅度,都是数据,而打扫的过程是较为复杂的算法过程,总之,也是算法,即程序的实现方式。jquery
另外,咱们还能够设置打扫时间等等初始数据。git
总之一句话,编程即数据结构和算法的结合。简单的程序可能不须要跟用户交互数据,可是现代的应用程序几乎都须要跟用户进行交互,不分应用程序类型,无论是CS型仍是BS型的程序都是如此,而Python最擅长的Web App即BS型的程序,就是经过url和http来跟用户进行数据交互的,经过url和http请求,用户能够操做服务器端的程序,主要操做分为:增、删、改、查几类。github
在开始以前,咱们回顾一下我们以前写过的图书管理系统项目,请仔细回想一下,对于该项目的设计,咱们大概是如下面这种方式来实现的web
理论上来讲,这种方式彻底能够实现咱们的需求,可是一旦项目丰富起来,随着数据量增长,随着各个业务系统之间的逻辑关系不断的复杂,url会愈来愈复杂,理论上来讲,无论是什么类型、什么名称的url都能指向具体的业务逻辑(视图函数),从而实现业务需求,可是若是没有明确的规范,因每一个人的思惟方式不同、命名方式不同而致使的url很是的乱,不方便项目的后期维护和扩展。ajax
以上这些状况的出现,致使了不少的问题,让互联网的世界变得杂乱不堪,日益复杂且臃肿。所以http协议创始人警告咱们这些凡人们正在错误的使用http协议,除了警告,他还发表了一篇博客,大概意思就是教你们如何正确使用http协议,如何正肯定义url,这就是REST(Representational State Transfer),不须要管这几个英文单词表明什么意思,只须要记住下面一句话:算法
用url惟必定位资源, 用Http 请求方式(GET,POST,PUT,DELETE) 描述用户行为django
根据这句话,咱们从新定义图书管理系统中的url编程
能够看到, url很是简洁优雅 , 不包括任何操做, 不包括任何动词 , 简简单单 , 用来描述服务器中的资源而已 , 服务器根据用户的请求方式对资源进行各类操做 , 而对数据的操做 , 最多见的就是CRUD(建立,读取,更新,删除),经过不一样的请求方式,就足够描述这些操做方式了。若是不够用,Http还有其余的请求方式呢!好比:PATCH,OPTIONS,HEAD, TRACE, CONNECT。
每一种请求方式的返回结果不一样 ,
{ "error": "Invalid API key" }
经过一个字典,返回错误信息。
这就是REST,上图中的url就是根据REST规范进行设计的RESTful api。
请看以下代码,基于类的视图,须要经过视图类调用as_view()这个类方法,从而实现请求的分发。
urls.py部分:
from django.urls import path from classbasedview import views urlpatterns = [ path('login/', views.LoginView.as_view()), ]
view.py部分
咱们须要引入View这个类,from django.views import View
from django.shortcuts import render, HttpResponse from django.views import View # Create your views here. class LoginView(View): def get(self, request): return render(request, 'classbasedview/login.html') def post(self, request): return HttpResponse("post")
这样就实现了一个基于类的视图,请你们之后都使用基于类的视图,这是生产环境中经常使用的一种方式,以前使用基于函数的视图,是为了方便你们学习理解,基于类的视图采用了面向对象编程思路,这种CBV的方式更加灵活,方便维护和扩展,之后,若是咱们须要在url和处理请求的逻辑之间绑定关系的过程当中自定制更多的操做,都会简单方便。后面的课程要介绍的Django RestFramework也正是依赖这种设计思路,来进行的功能扩展。
在学习面向对象时,咱们学习过classmethod,它是一个装饰器,表示容许类直接调用该方法而不用传入实例对象,以下代码所示:
# -*- coding: utf-8 -*- from django.utils.decorators import classonlymethod class Person(object): def __init__(self, name, age): self.name = name self.age = age def show_info(self): print("show info method been executed") @classmethod def show_info2(cls): print("show info2 method been executed") @classonlymethod def show_info3(cls): print("show info3 method been executed") p1 = Person("pizza", 18) # 普通方法能够经过实例对象和类调用 # 经过实例调用时默认传入self做为第一个参数,不须要手动传入参数 # 经过类调用时,必须手动指定实例对象传递给该方法 p1.show_info() Person.show_info(p1) # 被classmethod装饰器装饰后的方法能够经过实例对象和类直接调用 # 默认传入类名做为第一个参数 p1.show_info2() Person.show_info2() p1.show_info3() # 报错,Django框架实现了classonlymethd,再也不容许实例对象调用被该装饰器装饰的方法 Person.show_info3()
一样的,在学习面向对象时,咱们学习过属性判断、属性查找、属性设置,接下来,再回顾一下:
class Person(object): def __init__(self, name, age): self.name = name self.age = age self._hobby = 'girls' def show_info(self): print("show info method been executed") p1 = Person("pizza", 18) # 查看该对象是否有show_info方法 print(hasattr(p1, "show_info")) # 查看该对象是否有age属性 print(hasattr(p1, "age")) print(hasattr(p1, "hahaha")) greeting = "Hello World" # 设置属性 setattr(p1, "hahaha", greeting) print(hasattr(p1, "hahaha")) func = getattr(p1, "show_info") print(func) # <bound method Person.show_info of <__main__.Person object at 0x102219d68>> # 注意:直接调用,不须要传入self,getattr时已经绑定self到func了 func() print(hasattr(Person, "show_info")) # True print(hasattr(Person, "name")) # False print(hasattr(Person, "country")) # False # 给Person类设置属性 setattr(Person, "country", "china") print(hasattr(Person, "country")) # True print(hasattr(p1, "country")) # True
关于self到底指向哪里,咱们再回顾一下:
# -*- coding: utf-8 -*- class Request(object): def show_request(self): print(self) class NewRequest(Request): def show_request(self): print(self) request = NewRequest() # 调用方法的实例对象是哪一个,self就指向哪一个对象 request.show_request()
Http请求报文包含三部分:分别是请求行、请求报文头、请求报文体
POST /classbasedview/login/ HTTP/1.1 Host: 127.0.0.1:9001 Connection: keep-alive Content-Length: 114 Cache-Control: max-age=0 Origin: http://127.0.0.1:9001 Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Referer: http://127.0.0.1:9001/classbasedview/login/ Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Cookie: csrftoken=xPHeedcs8duBuCv031bCvsG1zMX1aGNpKlsPdCkhQHICd4lvMeHIEwkpJUQbLiOl; is_login=True; username=alex; last_time="2018-11-24 13:37:33"; fruit=xiangjiao
注意 : post 请求类型有formdata , 而get没有 , 下面详细介绍几个常见的请求头信息:
下面是请求报文体
csrfmiddlewaretoken=EqHslTSOeI6TWMXwmFCTuLLeflkjWSJTgUdLeFw1Xtxp5S1ea8vYo3IOX7DEPnO4&username=pizzali&password=aaa
同的Content-Type,请求报文体的格式不同,application/x-www-form-urlencoded使用&符合来拼接请求的键值对,最后,本质上,仍是经过socket将以上内容编码成字节数据,发送到服务器端,服务器则根据Content-Encoding先将数据总体解码,以后再经过Content-Type指定的编码协议来读取请求数据。
通俗一点理解,咱们可使用任何链接符(&)或者(……),只要服务器端还客户端互相承认便可。由于协议就是为了方便沟通双方进行信息交互的。
最后,咱们来复习一下JavaScript中的object数据类型和Json类型时间的互相转换,Json数据是网络传输中很是流行且使用普遍的数据格式,咱们知道在Python中,将一个字典转换为Json格式,使用以下方式:
>>> dict_data = { "name": "Pizza", "age": 18 } >>> import json # 经过json模块的json.dumps(data)方式将Python中的字典类型转换为json类型 >>> json_data = json.dumps(di) >>> print(type(json_data)) <class 'str'>
而如何将Json数据转换为Python的字典类型呢?请看下面的代码:
# 经过json.loads(data)能够将json数据转换为Python中的字典类型 >>> dict_data2 = json.loads(json_data) >>> print(type(dict_data2)) <class 'dict'>
那么在JavaScript中,若是实现将数据类型(数组,object)转换为Json类型呢?请看下图
REST 是一种软件架构设计风格 , 不是标准 , 也不是具体的技术实现 , 只是提供了一组设计原则和约束条件 , 是目前最流行的API 设计规范 , 用于Web 数据接口的设计 , 2000年 , 由Roy Fielding在他的博士论文中提出,Roy Fielding是HTTP规范的主要编写者之一。
那么,咱们所要讲的Django RestFramework与rest有什么关系呢?
其实 , DRF(Django RestFramwork) 是一套基于Django开发的 , 帮助咱们更好的设计符合REST 规范的Web 应用的一个Django App , 因此 , 在本质上 , 他是一个Django App
从概念就能够看出 , 有了这样的一个App , 可以帮助咱们更好的设计符合RESTful 规范的Web应用 , 实际上 , 没有它 , 咱们也能本身设计符合规范的Web应用 , 下面的代码显示如何手动实现符合Restful 规范的Web应用 ,
class CoursesView(View): def get(self, request): courses = list() for item in Courses.objects.all(): course = { "title": item.title, "price": item.price, "publish_date": item.publish_date, "publish_id": item.publish_id } courses.append(course) return HttpResponse(json.dumps(courses, ensure_ascii=False))
如上代码所示,咱们获取全部的课程数据,并根据REST规范,将全部资源的经过对象列表返回给用户。可见,就算没有DRF咱们也可以设计出符合RESTful规范的接口甚至是整个Web App,可是,若是全部的接口都自定义,不免会出现重复代码,为了提升工做效率,咱们建议使用优秀的工具。DRF就是这样一个优秀的工具,另外,它不只仅可以帮助咱们快速的设计符合REST规范的接口,还提供诸如认证、权限等等其余的强大功能。
前面提到,REST是目前最流行的 API 设计规范,若是使用Django开发你的Web应用,那么请尽可能使用DRF,若是使用的是Flask,可使用Flask-RESTful。
DRF官方文档中有详细介绍。使用以下命令安装,首先安装Django,而后安装DRF:
>>> pip install django
>>> pip install djangorestframework
安装完成以后,咱们就能够开始使用DRF框架来实现我们的Web应用了,这部份内容包括以下知识点:
介绍DRF,必需要介绍APIView,它是重中之重,是下面全部组件的基础,由于全部的请求都是经过它来分发的,至于它到底是如何分发请求的呢?想要弄明白这个问题,咱们就必须剖析它的源码,而想要剖析DRF APIView的源码,咱们须要首先剖析django中views.View类的源码,为何使用视图类调用as_view()以后,咱们的请求就可以被不一样的函数处理呢?接下来请同窗们思考十分钟时间,本身尝试分析一下django中views.View类的源码?稍后我请同窗来描述一下整个流程。
好了,同窗们,接下来,咱们一块儿来分析一下django中views.View类的源码。请看下图:
请仔细回想咱们前面回顾的知识点 , 源码中最后会经过getattr 在self 中 查找request.method.lower() , 也就是get , post或者delete 这些方法中的一个 , 那么 , self 是谁 , 就是相当重要的一点,前面讲到过,谁调用类中的方法,self就指向谁,此时,一层层往回找,咱们会发现,self = cls(**initkwargs),self就是咱们视图类的实例化对象,因此,dispatch函数确定会到该视图类中找对应的方法(get或者post)。
接下来是提问时间,请问若是有以下函数,我想经过不修改函数内容的状况下,如何给函数新增一个统计执行时间的功能:
def outer(func): def inner(*args, **kwargs): import time start_time = time.time() ret = func(*args, **kwargs) end_time = time.time() print("This function elapsed %s" % str(end_time - start_time)) return ret return inner @outer def add(x, y): return x + y
这是函数,若是是类呢?面向对象编程,如何扩展你的程序,好比有以下代码:
class Person(object): def show(self): print("Person's show method executed!") class MyPerson(Person): def show(self): print("MyPerson's show method executed") super().show() mp = MyPerson() mp.show()
这就是面向对象的程序扩展,如今你们是否对面向对象有了更加深入的认识呢?接下来给你们十分钟时间,消化一下上面两个概念,而后请思考,那么假设你是Django RestFramework的开发者,你想自定制一些本身想法,如何实现。
好了,相信你们都已经有了本身的想法,接下来,咱们一块儿来分析一下,Django RestFramework的APIView是如何对Django框架的View进行功能扩展的。
from django.shortcuts import HttpResponse import json from .models import Courses # 引入APIView from rest_framework.views import APIView # Create your views here. class CoursesView(APIView): # 继承APIView而不是原来的View def get(self, request): courses = list() for item in Courses.objects.all(): course = { "title": item.title, "description": item.description } courses.append(course) return HttpResponse(json.dumps(courses, ensure_ascii=False))
其余的保持不变,完整的请求流程请看下图:
以上就是Django RestFramwork APIVIew 的请求处理流程 , 咱们能够经过重写dispatch() 方法或者重写 as_view() 方法 来自定制本身的想法 .
那么 , Django RestFramwork 到底自定制了哪些内容呢? 在本文的最开始,咱们已经介绍过了,就是那些组件,好比解析器组件、序列化组件、权限、频率组件等。
接下来,咱们就开始介绍Django RestFramework中的这些组件,首先,最基本的,就是解析器组件,在介绍解析器组件以前,我提一个问题,请你们思考,如何发送Json格式的数据给后端服务器?
好了,时间到,请看下面的代码,经过ajax请求,咱们能够发送json格式的数据到后端:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> <script src="/static/jquery-1.10.2.min.js"></script> </head> <body> <form action="" method="post" enctype="application/x-www-form-urlencoded"> {% csrf_token %} 用户名: <input type="text" name="username"/> 密码: <input type="password" name="password"/> 提交: <input type="submit" value="提交"/> </form> <hr> <button class="btn">点击发送Ajax请求</button> <script> $(".btn").click(function () { $.ajax({ url: '', type: 'post', contentType: 'application/json', data: JSON.stringify({ username: "alex", password: 123 } ), success: function (data) { console.log(data); } }) }) </script> </body> </html>
经过上文的知识点复习咱们已经知道,Content-Type用来定义发送数据的编码协议,因此,在上面的代码中,咱们指定Content-Type为application/json,便可将咱们的Json数据发送到后端,那么后端如何获取呢?
按照以前的方式 , 咱们使用request.POST , 若是打印该值 , 会发现是一个空对象 : request.POST <QueryDict:{}>, 该现象证实Django 并不能处理请求协议为 application/json 编码协议的数据 , 咱们能够去看看request源码 , 能够看到下面这一段 :
if self.content_type == 'multipart/form-data': if hasattr(self, '_body'): # Use already read data data = BytesIO(self._body) else: data = self try: self._post, self._files = self.parse_file_upload(self.META, data) except MultiPartParserError: # An error occurred while parsing POST data. Since when # formatting the error the request handler might access # self.POST, set self._post and self._file to prevent # attempts to parse POST data again. # Mark that an error occurred. This allows self.__repr__ to # be explicit about it instead of simply representing an # empty POST self._mark_post_parse_error() raise elif self.content_type == 'application/x-www-form-urlencoded': self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict() else: self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict()
可见 Django 原生解析器并不处理application/json 编码协议的数据请求 , 好了 , 有了这样的认识以后 , 我们就能够开始正式介绍DRF的解析器了 , 解析器 , 顾名思义 , 就是用来解析数据的请求的
不过咱们将在下一节课介绍Django RestFramework的解析器组件。虽然Django的原生解析器不支持application/json编码协议,可是咱们能够经过拿到原始的请求数据(request.body)来手动处理application/json请求,虽然这种方式不方便,也并不推荐,请看以下代码:
class LoginView(View): def get(self, request): return render(request, 'classbasedview/login.html') def post(self, request): print(request.POST) # <QueryDict: {}> print(request.body) # b'{"username":"alex","password":123}' data = request.body.decode('utf-8') dict_data = json.loads(data) username = dict_data['username'] password = dict_data['password'] return HttpResponse(json.dumps(dict_data))
经过上面的代码,咱们能够经过request.body手动处理application/json请求,不过,如上文所说,并不推荐。
今天最后一项内容,就是postman这个工具的介绍,咱们经过postman来模拟用户请求,简单方便,再也不须要使用浏览器来发请求了。
从POSTMAN官网下载以后安装便可。
请看下面的图片:
如图所示,在下拉框中选择GET方式发送请求,在地址栏输入请求url,而后点击send发送便可发送GET请求到后端服务器。
请看下图:
选择POST,输入地址,而后在下面的选项框中选择Body,以后选择x-www-form-urlencoded协议,输入key和value,点击Send,便可以urlencoded方式发送post请求,而且在最下面的提示框中能够查看到服务器返回的信息。
最后,请看下图,以Json协议发送POST请求:
前面都一致,在Body框中,选择raw选项,而后选择application/json,最后输入Json格式的数据,点击Send便可以Json协议发送POST请求。
参考 : 文章