目录
- 一、web应用模式
- 二、API接口
- 三、接口测试工具postman
- postman介绍
- postman下载与使用
- 四、如何在浏览器中测试
- 五、restful规范(重要)
- 六、序列化反序列化
- 七、基于Django原生编写五个接口
- 八、drf介绍和快速使用
- 概念
- 特点(了解一下)
- 安装
- 使用drf编写五个接口
- 九、drf之APIView源码分析
- 基于APIView写五个接口
- CBV源码分析
- APIView执行流程-源码分析
一、web应用模式
- Django框架就是一种web框架,专门用来写web项目,之前学的,写的BBS项目,图书管理系统,用的都是前后端混合开发
-后端人员,写后端,也要写【模板语法】---》xx.html的python代码 -全栈开发-->前后端混合时代,比较多
- 从今天开始,学的是前后端分离
-后端人员,一点前端都不需要动,只需要写接口即可 -全栈开发---》web后端,前端框架(vue,react)
在开发Web应用中,有两种应用模式:
1.前后端不分离
客户端看到的内容和所有界面效果都是由服务端提供出来的
2.前后端分离【把前端的界面效果(html
,css
,js
分离到另一个服务端,python服务端只需要返回数据即可)】
前端形成一个独立的网站,服务端构成一个独立的网站
二、API接口
为了在团队内部形成共识、防止个人习惯差异引起的混乱,我们需要找到一种大家都觉得很好的接口实现规范,而且这种规范能够让后端写的接口,用途一目了然,减少双方之间的合作成本【前后端合作】
API概念
API:应用程序接口(API:Application Program Interface
)
应用程序接口(API:application programming interface)是一组定义、程序及协议的集合,通过 API 接口实现计算机软件之间的相互通信。API 的一个主要功能是提供通用功能集。程序员通过使用 API 函数开发应用程序,从而可以避免编写无用程序,以减轻编程任务
简单来说就是通过网络,规定了前后端信息交互规定的URL链接,也就是前后端信息交互的媒介
http://127.0.0.1/books/ 点赞,点踩 就是API接口
URL:长得像返回数据的URL链接
https://api.map.baidu.com/place/v2/search请求方式:
get
、post
、put
、patch
、delete
采用get
方式请求上方接口请求参数:
json
或xml
格式的key-value类型数据ak
:6E823f587c95f0148c19993539b99295
region:上海
query:肯德基
output:json
-早些年 前后端交互使用xml格式----》ajax:异步JavaScript和XML-后来,随着json格式的出现,成了主流,直到现在-以后:一定会出现,比json更高效的交互格式,更安全
- 响应结果:json或xml格式的数据
- 上方请求参数的output参数值决定了响应数据的格式
- 数据
# xml格式https://api.map.baidu.com/place/v2/search?ak=6E823f587c95f0148c19993539b99295®ion=%E4%B8%8A%E6%B5%B7&query=%E8%82%AF%E5%BE%B7%E5%9F%BA&output=xml'''中文会变成上面的样式,上下两个url是一样的意思'''https://api.map.baidu.com/place/v2/search?ak=6E823f587c95f0148c19993539b99295®ion=上海&query=肯德基&output=xml#json格式https://api.map.baidu.com/place/v2/search?ak=6E823f587c95f0148c19993539b99295®ion=%E4%B8%8A%E6%B5%B7&query=%E8%82%AF%E5%BE%B7%E5%9F%BA&output=jsonhttps://api.map.baidu.com/place/v2/search?ak=6E823f587c95f0148c19993539b99295®ion=上海&query=肯德基&output=json'''下方是返回的值'''{ "status":0, "message":"ok", "results":[ { "name":"肯德基(罗餐厅)", "location":{ "lat":31.415354, "lng":121.357339 }, "address":"月罗路2380号", "province":"上海市", "city":"上海市", "area":"宝山区", "street_id":"339ed41ae1d6dc320a5cb37c", "telephone":"(021)56761006", "detail":1, "uid":"339ed41ae1d6dc320a5cb37c" } ...]}
三、接口测试工具postmanpostman介绍
作为后端,写好了接口,我们自己要测试通过,再给别人用。
而后端测试人员需要测试API接口的时候,总不可能在浏览器里面试用吧,所以有了专业的接口测试工具Postman
-浏览器只能发送get请求,不能自动发送post,delete请求 -postman---》开源软件,只是谷歌浏览器的插件,越做好好,后来可以按装到操作系统上,再后来,收费 -postwoman -很多很多其他的,不同公司用的也可能不一样,你只需要明白一个点,这个工具只是用来发送http请求
postman下载与使用
1、官方下载
https://www.postman.com/downloads/?utm_source=postman-home—-》Postman-win64-Setup.exe
2、安装时,双击即可,马上就会出现下图界面
3、使用讲解
打开一个新的测试页面后界面如下,返回数据的窗口在没有发送请求之前是空的。
点击界面中间的Body,可以选择发送请求的方式,需要注意在使用form-data请求方式,即用form表单传输文件的时候,需要手动选择传输的数据类型
这里返回的结果
四、如何在浏览器中测试
我们需要再settings.py
文件中注册rest_framework
才能在浏览器中进行接口测试
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', 'rest_framework',]
五、restful规范(重要)
概念
REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征性状态转移)。 它首次出现在2000年Roy Fielding的博士论文中
RESTful是一种定义Web API接口的设计风格,尤其适用于前后端分离的应用模式中
这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源
事实上,我们可以使用任何一个框架都可以实现符合restful规范的API接口
十条规范
-1 数据的安全保障,通常使用https(http+ssl/tsl)协议 -url链接一般都采用https协议进行传输 -采用https协议,可以提高数据交互过程中的安全性 -2 接口中带api标识-https://api.lqz.com/books(可以写在域名中) -https://www.lqz.com/api/books(也可以写在路由中) 咱们用这个 -3 多版本共存,路径中带版本信息-https://api.lqz.com/v1/login -https://api.lqz.com/v2/login-https://www.lqz.com/api/v1/login -https://www.lqz.com/api/v2/login -4 数据即是资源,均使用名词,尽量不出现动词(最核心的)-接口一般都是用于完成前后台数据的交互,交互的数据我们称之为资源 -接口形式如下 https://api.baidu.com/users https://api.baidu.com/books -特殊的接口可以出现动词,因为这些接口一般没有一个明确的资源,或是动词就是接口的核心含义、https://api.baidu.com/login -5 资源操作由请求方式决定(method)-操作资源一般都会涉及到增删改查,我们提供请求方式来标识增删改查动作 https://api.baidu.com/books - get请求:获取所有书(获取所有) https://api.baidu.com/books/1 - get请求:获取主键为1的书(获取一条) https://api.baidu.com/books - post请求:新增一本书书(新增一条) https://api.baidu.com/books/1 - put请求:修改主键为1的书(整体修改一条) https://api.baidu.com/books/1 - patch请求:修改主键为1的书(局部修改一条) 'ps:patch用的不多,一般用put即可' https://api.baidu.com/books/1 - delete请求:删除主键为1的书(删除一条) -6 在请求地址中带过滤条件 https://api.example.com/v1/zoos?limit=10:指定返回记录的数量 https://api.example.com/v1/zoos?offset=10:指定返回记录的开始位置 https://api.example.com/v1/zoos?page=2&per_page=100:指定第几页,以及每页的记录数 https://api.example.com/v1/zoos?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序 https://api.example.com/v1/zoos?animal_type_id=1:指定筛选条件 -7 响应中状态码:两套-http响应状态码:1xx:请求正在处理,2xx:成功响应,3xx:重定向,4xx:客户端错误,5xx:服务端错误 '常见的需要记住' 响应状态码2XX200:常规请求201:创建成功 响应状态码3XX301:永久重定向302:暂时重定向 响应状态码4XX403:请求无权限404:请求路径不存在405:请求方法不存在 响应状态码5XX500:服务器异常 -https://blog.csdn.net/li_chunlong/article/details/120787872 -公司内部规定的响应状态码,放在响应体中 {code:0} 咱们后期一般使用100 101 102这种 -8 返回数据中带错误信息 { code:0 msg: "ok/用户名错误"} -9 返回的结果应该符合以下规范---》好多公司不遵循这个 GET 获取所有数据:返回资源对象的列表(数组)[{name:红楼梦,price:99},{name:红楼梦,price:99},{name:红楼梦,price:99}] GET 单个对象:返回单个资源对象:{name:红楼梦,price:99} POST 新增对象:返回新生成的资源对象:{name:西游记,price:99} PUT 修改对象:返回完整的资源对象 :{name:西游记,price:100} DELETE 删除:返回一个空文档 -10 响应数据中带连接 # Hypermedia API,RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么{ "status": 0, "msg": "ok", "results":[ { "name":"肯德基(罗餐厅)", "img": "https://image.baidu.com/kfc/001.png" } ...]}比较好的接口返回# 响应数据要有状态码、状态信息以及数据本身{ "status": 0, "msg": "ok", "results":[ { "name":"肯德基(罗餐厅)", "location":{ "lat":31.415354, "lng":121.357339 }, "address":"月罗路2380号", "province":"上海市", "city":"上海市", "area":"宝山区", "street_id":"339ed41ae1d6dc320a5cb37c", "telephone":"(021)56761006", "detail":1, "uid":"339ed41ae1d6dc320a5cb37c" } ...]}
六、序列化反序列化
api接口开发,最核心最常见的一个过程就是序列化,所谓序列化就是把【数据转换格式】,序列化可以分两个阶段:
序列化: 把我们识别的数据转换成指定的格式提供给别人
-字典,列表------》json格式存到文件中了-例如:我们在django中获取到的数据默认是模型对象,但是模型对象数据无法直接提供给前端或别的平台使用,所以我们需要把数据进行序列化,变成字符串或者json数据,提供给别人-read
反序列化:把别人提供的数据转换/还原成我们需要的格式
-例如:前端js提供过来的json数据,对于python而言就是字符串,我们需要进行反序列化换成模型类对象,这样我们才能把数据保存到数据库中。 -write
七、基于Django原生编写五个接口
# 以后写的接口,基本上都是五个接口及其变形-查询所有-查询单个-新增一个-修改一个-删除一个# 基于books单表为例,写五个接口-创建book表-表迁移-录入假数据:直接录,后台管理录-写查询所有接口----》遵循restful规范,使用cbv-新增一条数据-----》-查询一条-修改一条:put提交的数据,不能从request.POST中取-删除一条
models.py
form django.db import modelsclass Book(models.Model):name = models.CharField(max_length=32)price = models.CharField(max_length=32)publish = models.CharField(max_length=32)
urls.py
urlpatterns = [path('admin/', admin.site.urls),path('api/v1/books/', views.BookView.as_view()),path('api/v1/books//', views.BookDetailView.as_view()),]
views.py
from django.shortcuts import render,HttpResponse,redirectfrom django.http import JsonResponsefrom .models import Book# 相对导入from django.views import Viewimport jsonclass BookView(View):# 查询所有def get(self, request):# 查询出所有的图书,queryset对象,不能直接给前端books = Book.objects.all()# 转成json格式,给前端# 把quer对象转成列表,然后再使用JsonResponsebook_list = []for book in books:book_list.append({'name': book.name, 'price': book.price, 'publish': book.publish})# 扩展做了解# return HtpResponse(json.dumps(book_list, ensure_ascii=False))# 指定ensure_ascii为false,前端就显示中文了return JsonResponse(book_list, safe=False, json_dumps_params={'ensure_ascii': False})# JsonRsponse只能放字典或列表# 薪增一个(只能使用urlencoded或form-data编码,使用json形式编码不行,因为json格式编码提交的数据,不能从request.POST中取,从body中)def post(self, request):# 取出前端传入的数据name = request.POST.get('name')price = reqeust.POST.get('price')publish = request.POST.get('publish')# 存到数据库中book = Book.objects.create(name=name, price=price, publish=publish)# 返回新增的对象字典return JsonResponse({'name': book.name, 'price': book.price, 'publish': book.publish})class BookDetailView(View):def get(self, request, pk):book = Book.objectsfilter(pk=pk).first()# book模型对象转成字典,使用JsonResponse返回return JsonResponse({'id': book.id, 'name': book.name, 'price': book.price, 'publish': book.publish})def put(self, request, pk):# request.POST只能取post提交的urlencoded或form-data编码数据,put提交的取不到# 查到要改的book = Book.objects.filter(pk=pk).first()# 取出前端传入的数据,修改完,保存----》存在问题,因为put提交的取不到# book.name = request.POST.get('name')# book.price = request.POST.get('price')# book.publish = request.POST.get('publish')# book.save()# 前端使用json格式提交,自己保存print(request.body)book_dict=json.loads(request.body)book.name = book_dict.get('name')book.price = book_dict.get('price')book_publish = book_dict.get('publish')book.save()return JsonResponse({'id': book.id, 'name': book.name, 'price': book.price, 'publish': book.publish})def delete(self, request, pk):Book.objects.filter(pk=pk).delete()return JsonResponse(data={})
八、drf介绍和快速使用概念
核心思想: 缩减编写api接口的代码
Django REST framework
是一个建立在Django基础之上的Web 应用开发框架,可以快速的开发REST API接口应用。在REST framework中,提供了序列化器Serialzier的定义,可以帮助我们简化序列化与反序列化的过程,不仅如此,还提供丰富的类视图、扩展类、视图集来简化视图的编写工作。REST framework还提供了认证、权限、限流、过滤、分页、接口文档等功能支持。REST framework提供了一个API 的Web可视化界面来方便查看测试接口。
官方文档:https://www.django-rest-framework.org/
github: https://github.com/encode/django-rest-framework/tree/master
特点(了解一下)
- 提供了定义序列化器Serializer的方法,可以快速根据 Django ORM 或者其它库自动序列化/反序列化;
- 提供了丰富的类视图、Mixin扩展类,简化视图的编写;
- 丰富的定制层级:函数视图、类视图、视图集合到自动生成 API,满足各种需要;
- 多种身份认证和权限认证方式的支持;[jwt]
- 内置了限流系统;
- 直观的 API web 界面;
- 可扩展性,插件丰富
安装
# djangorestframework: drf 帮助我们快速的实现符合restful规范的接口# django 最新 4.x ,一般都会用最新版的上一版3.x# drf最新支持到djagno 3.x ,最新不支持2.x# 安装drfpip3 install djangorestframework -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com/simple/ # 由于你是django2.x 它发现它不支持,它会自动写在dajgno,安装最新的django 4.x
使用drf编写五个接口
注意:url的末尾必须要写斜杠符号,不然会报错
urls.py
from app01.views import BookViewfrom rest_framework.routers import SimpleRouterrouter = SimpleRouterrouter = SimpleRouter()router.register('books', BookView, 'books')urlpatterns = [path('admin/', admin.site.urls),]# 两个列表相加 [1,2,4] + [6,7,8]=urlpatterns += router.urls
views.py
form .serializer import BookSerializerfrom rest_framework.viewsets import ModelViewSetclass BookView(ModelViewSet):queryset = Book.objects.all()serializer_class = BookSerializer
serializer.py
from rest_framework import serializersform .models import Bookclass BookSerializer(serializers.ModelSerializer):class Meta:model = Bookfields = '__all__'
九、drf之APIView源码分析基于APIView写五个接口
urls.py
urlpatterns = [path('books/', BookView.as_view()),path('books//', BookDetailView.as_view()),]
views.py
form rest_framework.views import APIView# APIView继承了Django原来的Viewfrom .serializer import BookSerializerfrom rest_framework.response import Responseclass BookView(APIView):# 查询所有def get(self, request):book_list = Book.objects.all()# drf提供了序列化类ser = BookSerializer(instance=book_list, many=True)# 序列化return Response({'code': 100, 'msg': '成功', 'result': ser.data})def post(self, request):ser = BookSerializer(data=request.data)# 反序列化if ser.is_valid():# 数据校验---》有些不合法禁止ser.save()# 保存到数据库中return Response({'code': 100, 'msg': '成功'})class BookDetailView(APIView):# # 查询单条def get(self, request, pk):book = Book.objects.filter(pk=pk).first()ser = BookSerializer(instance=book,many=False)# 序列化return Response({'code': 100, 'msg': '成功', 'result': ser.data})# 修改一条def put(self, request, pk):book = Book.objects.filter(pk=pk).first()ser = BookSerializer(instance=book,data=request.data)# 反序列化if ser.is_valid():# 数据校验---》有些不合法禁止ser.save()# 保存到数据库中return Response({'code': 100, 'msg': '成功'})def delete(self, request, pk):Book.objects.filter(pk=pk).delete()return Response({'code': 100, 'msg': '删除成功'})
serializer.py
from rest_framework import serializersfrom .models import Bookclass BookSerializer(serializers.ModelSerializer):class Meta:model = Bookfields = '__all__'
CBV源码分析
# cbv写法:1 视图中写视图类,继承View,写跟请求方式同名的方法 class BookView(View): def get(self,request): return 四件套 2 在路径用写 path('books/', BookView.as_view()) # 如上写法,为什么能够执行# 前置条件:前端请求,一旦路径匹配成功,就会执行 BookView.as_view()(request传入,)# 入口在 BookView.as_view()--->执行结果---》View中有个as_view类的绑定方法 @classmethod def as_view(cls, **initkwargs): def view(request, *args, **kwargs): self = cls(**initkwargs) res=self.dispatch(request, *args, **kwargs) return res return view # 执行结果是view 的内存地址: 请求来了,执行view(request)path('books/', view) # 执行 View类中的as_view方法中的内层的view函数,路由匹配成功,本质是在执行self.dispatch(request, *args, **kwargs) # self是谁的对象?BookView的对象 # 去BookView中dispatch,找不到,去父类,View中找到了 # View这个类的dispatch def dispatch(self, request, *args, **kwargs): # request.method.lower() 如果是get请求, ‘get’ 在这个列表里面 if request.method.lower() in self.http_method_names: # handler=getattr(BookView的对象,'get') # handler就是BookView类中的get方法 handler = getattr(self, request.method.lower()) else: handler = self.http_method_not_allowed # 执行 BookView类中的get方法 (request) return handler(request, *args, **kwargs) # 最终本质跟写fbv的执行流程一样# 最终结论:什么请求方式,就会执行视图类中的什么方法-本身请求来了,匹配成功,会执行view(request)def view(request, *args, **kwargs):return self.dispatch(request, *args, **kwargs)-self.dispatch View类的方法def dispatch(self, request, *args, **kwargs):# request.method请求方式转成小写,必须在列表中才能往下走if request.method.lower() in self.http_method_names:# 反射,去self【视图类的对象:BookView】,去通过get字符串,反射出属性或方法# BookView的get方法handler = getattr(self, request.method.lower(), self.http_method_not_allowed)else:handler = self.http_method_not_allowed# BookView的get方法,加括号,传入requestreturn hander(request, *args, **kwargs)
APIView执行流程-源码分析
urls.py
path('books/', views.BookView.as_view()),---》请求来了,执行views.BookView.as_view()()--->现在as_view是APIView的as_view(因为我们把父类从View换成了APIView)
我们点进源码查看可以发现APIView其实继承了View
class APIView(View):
往下查找as_view()的代码
# APIView的as_view方法:view还是原来的view,但是以后再也没有csrf认证了@classmethoddef as_view(cls, **initkwargs):# 调用父类的as_view,父类是Django原生view# 把Django原生view的as_view方法中的闭包函数view拿出来了view = super().as_view(**initkwargs)# csrf_exempt 排除所有csrf的认证# 相当于再所有的方法上面加了这个装饰器return csrf_exempt(view)'从他内部的view我们可以看到他是用派生方法调用父类View中的as_view因此我们说view还是原来的view,但是返回的结果是csrf_exempt(view),回顾装饰器的知识,我们可以知道虽然把装饰器写成了语法糖,但是在运行的时候其实就是把被装饰的函数名称传进来当参数运行,因此这里就是取消了csrf认证的作用'# 路由匹配成功,执行的其实就是csrf_exempt(view)(request)--->而这个view就是View的as_view中的闭包函数view---》然后执行self.dispatch--->self是视图类的对象---》BookView产生的对象---》这时候会执行self.dispatch,根据名称的查找顺序他会查找到APIView的dispatch:def dispatch(self, request, *args, **kwargs):# 这里的参数中的request是Django原生request,老的request# 把老的request包装成了新的request,这个是drf提供的Request类的对象request = self.initialize_request(request, *args, **kwargs):# 到此以后,这个request就是新的了,老的request在request._request这是老的# 把新的request放到了self对象【BookView的对象】self.request = reqeusttry:# 执行了三大认证【认证、频率、权限】,使用新的request,之后会将self.initial(request, *args, **kwargs)# 跟之前一模一样用反射获取执行结果,如果报错就用异常捕获处理if request.method.lower() in self.http_method_names:handler = getattr(self, request.method.lower(), self.http_method_not_allowed)else:handler = self.http_method_not_allowed# 把新的request传入了,视图类的方法中get的request也是新的response = handler(request, *atgs, **kwargs)except Exception as exc:# 在执行3大认证和视图类中方法的过程中,如果出了异常,都能捕获到---》全局异常捕获response = self.handle_exception(exc)self.response = self.finalize_response(request, response, *args, **kwargs)return self.response'上方的request调用了initialize_request方法,这个方法的返回结果产生了一个Request生成的对象,它的源码如下:'def initialize_request(self, request, *args, **kwargs):parser_context = self.get_parser_context(request)return Request(reqeust,parsers=self.get_parsers(),authenticators=self.get_authenticators(),negotiator=self.get_content_negotiator(),parser_context=parser_context)'接着我们点进这个Request类的源码,我们会发现老的request变成了_request属性'def __init__(self, request, parsers=None, authenticators=None, negotiator=None, parser_context=None):self.request = requestself.parsers = parsers or ()self.authenticators = authenticators or ()self.negotiator = negotiator or self._default_negotiator()self.parser_context = parser_contextself._data = Emptyself._files = Emptyself._full_data = Emptyself._content_type = Emptyself._stream = Empty# 总结:APIView的执行流程1.去除了所以的csrf2.包装了新的request,以后在视图类中用的request Request类的对象,不是原生的了-原生的在:新的request._request3.在执行视图类的方法之前,执行了三大认证4.如果三大认证或视图函数方法执行过程中出了错,会有异常捕获---》全局异常捕获5.以后视图类方法中的request都是新的# 补充:装饰器的基本原理def auth()# 装饰器def add()# 函数# 使用auth装饰add函数@auth# 本质是add=auth(add)def add()# 以后再使用add,其实就是在使用 auth(add) 的返回结果