drf(初始drf,restfull规范 ,CBV、APIView、Request源码)
# 前后端混合开发(前后端不分离):通过模版语法,在服务器上处理好html的内容(组合字符串),返回给浏览器一堆字符串(字符串封装到respons对象里),浏览器在渲染 # 前后端分离:只专注于写后端接口,返回json、xml格式 # xml比json笨重 # 补充:什么是动态页面(需要查数据库的),什么是静态页面(内容写死的页面) # 访问量大的home页面,进行静态页面优化
# 通过网络,规定了前后台信息交互规则的url链接,也就是前后台信息交互的媒介 Application Programming Interface
# postman是当前最好用的,模拟发送http请求的工具 # 解析json的网站 www.json.cn # 请求头中User-Agent:客户端的类型 # 请求头中加其他参数 # url的末尾加/,浏览器中不加,是因为两次get,重定向自动给加上去的
''' 1. 什么是RESTFUll RESTfull 是目前最流行的 API 设计规范,用于 Web 数据接口的设计。 REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移” REST从资源的角度类审视整个网络,它将分布在网络中某个节点的资源通过URL进行标识,客户端应用通过URL来获取资源的表征,获得这些表征致使这些应用转变状态 所有的数据,不过是通过网络获取的还是操作(增删改查)的数据,都是资源,将一切数据视为资源是REST区别与其他架构风格的最本质属性 '''
十条规范
1 数据的安全保障
-
url链接一般都采用https协议进行传输
注:采用https协议,可以提高数据交互过程中的安全性
抓包工具:fiddler,chales
2 接口特征表现
-
用api关键字标识接口url:
注:看到api字眼,就代表该请求url链接是完成前后台数据交互的
3 多数据版本共存
-
在url链接中标识数据版本
注:url链接中的v1、v2就是不同数据版本的体现(只有在一种数据资源有多版本情况下)
就比如手机app,有 些升级了,有些还没升级,没升级的,就是用的老版本
新升级的,用的新版本
4 数据即是资源,均使用名词(可复数)
-
接口一般都是完成前后台数据的交互,交互的数据我们称之为资源
注:一般提倡用资源的复数形式,在url链接中奖励不要出现操作资源的动词,错误示范:https://api.baidu.com/delete-user
-
特殊的接口可以出现动词,因为这些接口一般没有一个明确的资源,或是动词就是接口的核心含义
5 资源操作由请求方式决定
-
操作资源一般都会涉及到增删改查,我们提供请求方式来标识增删改查动作
-
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的书
-
https://api.baidu.com/books/1 - delete请求:删除主键为1的书
-
6 过滤信息(filtering,或称查询参数)
-
如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。下面是一些常见的参数。
https://api.baidu.com/v1/books?limit=10:指定返回记录的数量 https://api.baidu.com/v1/books?offset=10:指定返回记录的开始位置。 https://api.baidu.com/v1/books?page=2&per_page=100:指定第几页,以及每页的记录数。 https://api.baidu.com/v1/books?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。 https://api.baidu.com/v1/books?animal_type_id=1:指定筛选条件
7.响应状态码
7.1 正常响应
-
响应状态码2xx
-
200:常规请求
-
201:创建成功
-
7.2 重定向响应
-
响应状态码3xx
-
301:永久重定向
-
302:暂时重定向
-
7.3 客户端异常
-
响应状态码4xx
-
403:请求无权限
-
404:请求路径不存在
-
405:请求方法不存在
-
7.4 服务器异常
-
响应状态码5xx
-
500:服务器异常
-
8 错误处理,应返回错误信息,error当成key
发生错误时不要响应200状态码,有一种不恰当的做法是,即使发生错误,也返回200
状态码,把错误信息放在数据体里面,就像下面这样。
{ "status": "failure", "data": { "error": "Expected at least two items in list." } }
9 返回结果,针对不同操作,服务器向用户返回的结果应该符合一下规范
GET /collection:返回资源对象的列表(数组),一般是[{"id":1,"name":"a",},{"id":2,name:"b"},]这种类型 GET /collection/resource:返回单个资源对象, 一般是查看的单条数据 {"id":1,"name":'a'} POST /collection:返回新生成的资源对象 , 一般是返回新添加的数据信息, 格式一般是{} PUT /collection/resource:返回完整的资源对象 一般时返回更新后的数据,{} PATCH /collection/resource:返回完整的资源对象 DELETE /collection/resource:返回一个空文档 一般返回一个空字符串
10 Hypermedia API,提供链接
RESTful API最好做到Hypermedia,即返回结果中提供链接,API 的使用者未必知道,URL 是怎么设计的。
一个解决方法就是,在回应中,给出相关链接,便于下一步操作。
这样的话,用户只要记住一个 URL,就可以发现其他的 URL。
{ ... "feeds_url": "https://api.github.com/feeds", "followers_url": "https://api.github.com/user/followers", "following_url": "https://api.github.com/user/following{/target}", "gists_url": "https://api.github.com/gists{/gist_id}", "hub_url": "https://api.github.com/hub", ... }
# 安装:pip install djangorestframework=3.10.3 # 使用: 1 在settings.py 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' ] 2 在models.py中写表模型 class Book(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) price = models.DecimalField(max_digits=5, decimal_places=2) author = models.CharField(max_length=32) 3 新建一个序列化类 app01下建一个ser.py from rest_framework.serializers import ModelSerializer from app01.models import Book class BookModelSerializer(ModelSerializer): class Meta: model = Book fields = "__all__" 4 在视图函数中写视图类,views.py from rest_framework.viewsets import ModelViewSet from app01.models import Book from app01.ser import BookModelSerializer # Create your views here. class BookViewSet(ModelViewSet): queryset = Book.objects.all() serializer_class = BookModelSerializer 5 写路由关系,urls.py from app01 import views from rest_framework.routers import DefaultRouter router = DefaultRouter() # 可以处理视图的路由器 router.register('book', views.BookViewSet) # 向路由中注册视图集 # 将路由中的所有路由信息追到django的路由列表中 urlpatterns = [ path('admin/', admin.site.urls), ] # 两个列表相加,就是将列表rooter.urls的值追加到urlpatterns,for循环,再.append urlpatterns += router.urls
# ModelViewSet继承View(django原生View) # APIView继承View # 先读View # views.py from django.views import View class Books(View): def get(self, request): return HttpResponse('ok') # urls.py path('books/', views.Books.as_view()), ''' 在这个地方应该写个函数的内存地址,views.Books.as_view()执行完,是个函数内存地址,Books继承了Views类,里面的as_view是类方法,所有由Books.as_view() ''' http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] ''' 因return view,view是as_view的闭包函数,Books.as_view()的调用,就是调用view,类的绑定方法,把类当对象传入。 ''' @classonlymethod # 路由中,一般没有传参, **initkwargs没有值 def as_view(cls, **initkwargs): def view(request, *args, **kwargs): self = cls(**initkwargs) # 实例化,book对象 self.setup(request, *args, **kwargs) return self.dispatch(request, *args, **kwargs) # dispatch方法,先对象里找,没有去类中找,类中没有就去给类的父类找 return view def setup(self, request, *args, **kwargs): if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get # 传入的request,赋值给对象 self.request = request def dispatch(self, request, *args, **kwargs): if request.method.lower() in self.http_method_names: # 反射取值,得到get的内存地址 handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed # 内存地址加括号并传参,就是传参调用函数 return handler(request, *args, **kwargs)
七 APIView源码
1 一切皆对象
函数也是对象
def foo(a,b): return a+b foo.name = 'lq' # 一切皆对象 print(foo(2,3)) print(foo.name) # lq’
# 在视图函数上加装饰器@csrf_exempt # csrf_exempt(view) 这么写和在视图函数上加装饰器是一模一样
# urls.py path('booksapiview/', views.BooksAPIView.as_view()) # views.py from rest_framework.views import APIView class BooksAPIView(APIView): def get(self, request): return HttpResponse('ok') # APIView的as_view方法(类的绑定方法) @classmethod def as_view(cls, **initkwargs): # 调用父类,就是django的View类的as_view方法 view = super().as_view(**initkwargs) # 把函数当对象,进行属性赋值 view.cls = cls view.initkwargs = initkwargs # Note: session based authentication is explicitly CSRF validated, # 以后所有的请求,都没有csrf认证了,只要继承了APIView,就没有csrf的认证 # all other authentication is CSRF exempt. # 就是给函数加装饰器@csrf_exempt return csrf_exempt(view) # view = super().as_view(**initkwargs),--->django的View类中的as_view方法--->return self.dispatch(request, *args, **kwargs)--->回到APIView类的dispatch方法 # 类的方法调用,先从对象找--->产生对象的类中找--->父类--->父类的父类 def dispatch(self, request, *args, **kwargs): self.args = args self.kwargs = kwargs # 重新包装成一个request对象,以后再用的request对象,就是新的request对象了,initialize_request,是Request类的一个实例化的返回 request = self.initialize_request(request, *args, **kwargs) self.request = request self.headers = self.default_response_headers # deprecate? try: # 三大认证模块(request是新的request) self.initial(request, *args, **kwargs) # Get the appropriate handler method 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 # 响应模块 response = handler(request, *args, **kwargs) except Exception as exc: # 异常模块 response = self.handle_exception(exc) # 渲染模块,根据客户端的不同(浏览器,postman),渲染不同的数据 self.response = self.finalize_response(request, response, *args, **kwargs) return self.response
Request类的源码
# from rest-framework.request import Request # 只要继承了APIView,视图类中的request对象,都是新,也就是上面新的self.initialize_request(request, *args, **kwargs) # 原生的request在新的request._request class Request: def __init__(self, request, parsers=None, authenticators=None, negotiator=None, parser_context=None): # 二次封装request,将原生request作为drf-request对象的_request属性 self._request = request self.parsers = parsers or () self.authenticators = authenticators or () self.negotiator = negotiator or self._default_negotiator() self.parser_context = parser_context self._data = Empty self._files = Empty self._full_data = Empty self._content_type = Empty self._stream = Empty # 以后使用request对象,就像使用之前的request是一模一样(因为重写了__getattr__方法,request.methed用法是一样的,不用request._request.methed,request.属性的时,自动触发__getattr__方法 def __getattr__(self, attr): try: # 放射,从原生request对象中取属性方法 return getattr(self._request, attr) except AttributeError: # 改进,不仅可以点取属性,还可以用[]的形式取属性,类.__dict__ return self.__getattribute__(attr) # request.data 不是属性,是@property修饰的一个方法 # 还有返回的一个字典,post请求不管使用什么编码(urldecode、formdata、json),传过来的数据,都在request.data, @property def data(self): if not _hasattr(self, '_full_data'): self._load_data_and_files() return self._full_data # GET方发虽然重写了,还是request.GET, @property def query_params(self): return self._request.GET # 视图类中 print(request.query_params) ''' 也是GET过来的数据,作者给改了个名,认为query_params是查询集, 更符合drf规范,本来get过来的数据就是查询集 ''' print(request.GET)