DRF学习笔记

关于REST

什么是REST?全称是 Resource Representational State Transfer。通俗来讲就是:资源在网络中以某种表现形式进行状态转移。
分解开来:
Resource:资源,即数据(前面说过网络的核心)。比如 books;
Representational:某种表现形式,比如用JSON,XML,JPEG等;
State Transfer:状态变化。通过HTTP动词GET、PUT、POST等来实现。

以前的网页都是前后端融合到一体的。但近年来随着移动互联网的发展,各种类型的客户端层出不穷,导致需要很多种接口才能满足需求。这样不利于开发工作。故前后端分离模式应运而生。

API接口本质上就是一些预先定义好的函数,用以实现不同组件之间的交互。
API协议就是两个不同组件完成交互所遵循的规则。
 
前后端不分离:后端从数据库拿数据,渲染模板,返回的是HTML页面
前后端分离:后端对外提供统一的API接口,可对接浏览器或移动端APP。后端返回的是json或xml格式的数据,不负责前端页面的处理。(xml基本不用了)

而RESTful 风格的API提供了一套统一的接口来满足Web,IOS,Android的需求,非常适合于前后端分离的项目。

RESTful规范:
1、对于资源的具体操作类型由HTTP动词表示。/后指的是对应的SQL命令。常用四个:
get/select:从服务器取资源
post/create:从服务器新增资源
put/update:修改更新资源
delete/delete:从服务器删除资源
2、资源作为网址,使用与数据库表名相对应的名词复数形式,不能用动词;
如 GET/books
POST/books
PUT/books/1
DELETE/books/2
3、通过url参数的形式来过滤资源
如 GET /books/?limit=10
4、除了删除动作,其他都要求有响应体,尽量返回json格式的数据。
其他略、、、

RESTful风格的API的好处:

看Url就知道要什么;
看http method就知道干什么;
看http status code就知道结果如何。

下面使用django原生形式实现前后端分离的RESTful架构的API。

正常情况下,如果使用django去写一个图书管理的后端,需要很多接口。包括查询所有图书的接口,查询指定图书的接口,新增图书接口,修改图书的接口,删除图书的接口。这里用CBV类视图来实现。

根据路由后面有没有带pk/id可将这些接口分为两类,即不带pk的列表视图和带pk的详情视图。

列表视图内有get查询所有图书的接口和post新增单一图书的接口。如下:

 1 class BookListView(View):
 2     """列表视图"""
 3 
 4     def get(self,request):
 5         """查询所有图书接口"""
 6         # 1、查询所有图书模型
 7         books = Book.objects.all()
 8         # 2、遍历查询集,取出里面的每个书籍模型对象,转化为字典
 9         book_list = []
10         for book in books:
11             book_dict = {
12                 'nid':book.nid,
13                 'name':book.name,
14                 'price':book.price,
15                 'author':book.author
16             }
17             book_list.append(book_dict)
18         # 3、响应
19         return JsonResponse(book_list,json_dumps_params={'ensure_ascii':False},safe=False) # 将字典序列化为json
20         # JsonResponse类的data参数默认为字典,safe参数默认为true,如果不是字典则应该设置safe=False
21 # 注意:json_dumps_params参数默认为None,此时如果响应的字典中包含中文,则在客户端显示的是Unicode字符集的16进制码位,如u8c;
22 # 如果想要显示中文,则应将ensure_ascii的默认参数True改为False。
23 
24     def post(self,request):
25         """新增图书接口"""
26         # 要先在settings.py中关闭CSRF验证
27         # 1、获取json格式的请求数据的字节流,由utf-8编码的16进制表示 如x6c
28         json_bytes = request.body
29         # print(request.body)
30         # for k,v in request.environ.items():  # 打印请求头信息
31         #     print(k,v)
32         # 2、将字节流转化为json格式的字符串
33         json_str = json_bytes.decode()
34         # 3、将字符串转化为json格式的字典
35         json_dict = json.loads(json_str)
36         # 4、新增模型数据
37         book = Book(
38             name=json_dict['name'],
39             price=json_dict['price'],
40             author=json_dict['author']
41         )
42         # 5、保存模型
43         book.save()
44         # 6、响应字典
45         jd = {
46             'nid':book.nid,
47             'name':book.name,
48             'price':book.price,
49             'author':book.author
50         }
51         return  JsonResponse(jd,json_dumps_params={'ensure_ascii':False},status=201)

详情视图

 1 class BookDetailView(View):
 2     """详情视图"""
 3     def get(self,request,pk):
 4         """查询指定的图书接口"""
 5         # 1、获取指定pk的模型对象;
 6         try:
 7             book = Book.objects.get(nid=pk)  # id超出范围的异常处理
 8         except Book.DoesNotExist:
 9             print('查询数据不存在')
10             return HttpResponse("查询数据不存在", status=404)
11 
12         # 2、将模型对象转换为字典;
13         book_dict = {
14             'nid':book.nid,
15             'name':book.name,
16             'price':book.price,
17             'author':book.author
18         }
19         # 3、响应字典给前端
20         return JsonResponse(book_dict,json_dumps_params={"ensure_ascii":False})
21 
22     def put(self,request,pk):
23         """修改指定的图书接口"""
24         # 1、获取指定pk的模型对象
25         try:
26             book = Book.objects.get(nid=pk)  # id超出范围的异常处理
27         except Book.DoesNotExist:
28             return HttpResponse("要修改的数据不存在", status=404)
29         # 2、获取并将请求的数据转换为json字典
30         book_dict = json.loads(request.body.decode())
31         # 3 、修改模型对象并保存
32         book.name = book_dict['name']
33         book.price = book_dict['price']
34         book.author = book_dict['author']
35         book.save()
36         # 4、将修改后的模型对象转换为字典
37         book_modified_dict = {
38             'nid': book.nid,
39             'name': book.name,
40             'price': book.price,
41             'author': book.author
42         }
43         # 5、响应字典
44         return JsonResponse(book_modified_dict,json_dumps_params={'ensure_ascii':False})
45 
46     def delete(self,request,pk):
47         try:
48             book = Book.objects.get(nid=pk)
49         except Book.DoesNotExist:
50             return  HttpResponse('要删除的数据不存在',status=404)
51         book.delete()
52         return HttpResponse(status=204)  # 请求执行成功,但没有数据返回

DRF实现接口

从上面几个接口的代码可以看到,除了删除接口,其他的接口都有响应体,都会涉及到模型转字典这个操作,即序列化。新增和修改两个接口还会对请求体的数据进行字典转模型即反序列化操作,这就导致了代码的重复。
DRF将序列化和反序列化的业务逻辑进行了封装,简化了代码,大大提高了开发效率。
DRF是基于django的一个扩展。
安装:pip install djangorestframework。
注册:以django子应用的形式进行注册。

用drf来实现上面的图书接口,总共10行代码:

1、在urls.py中注册路由:

2、新建ser.py,添加序列化器:

3、在views.py中写入类视图:

浏览器访问:

 

 

为何如此简洁的代码就能实现接口功能呢?这就因为dfr内部对数据之间的转换进行了一系列的封装,这就涉及到序列化和反序列化。

序列化和反序列化

当需要给前端响应模型数据时,需要将模型数据序列化成前端需要的格式。

当需要将用户发送的数据存储到数据库时,就要将数据如字典、json、xml等反序列化成Django中的数据库模型类对象再保存。

这就是在开发RESTful API时,在视图中要做的最核心的事情。

。。。

DRF实例-book

。。。

查看部分源码

查看django原生view的源码:
 
 
 
查看APIView的源码:
 
CBV模式中的路由写法:
其中这个as_view()到底起了什么作用?
原来as_view()是django原生Views类的一个类函数。其层级关系如下:
该类函数返回了一个内部函数view(),在views.类名.as_view()中执行as_view()函数就是执行了这个view()函数。该函数主要是对CBV模式中的类的请求方法函数传递了请求对象的参数request和其他参数。
其中通过self.setup()的方式调用了as_view()下面的setup()函数,setup()函数中self.request = request里的self指的是调用setup()函数的那个实例对象即cls(),也就是self。所以我觉得setup()函数里面的三行代码完全可以直接写在as_view()里面,没必要单独抽出来放在新建的setup()里。
 
这个view()函数返回的是dispatch()函数的执行结果。这里类视图中并没有dispatch()函数,只能从父类View中寻找。其实就在as_view()的下边。
 
顺便补充hasattr()、getattr()和setattr()三个函数的用法:
1. hasattr(object, name)
  判断object对象中是否存在name属性,当然对于python的对象而言,属性包含变量和方法;有则返回True,没有则返回False;需要注意的是name参数是string类型,所以不管是要判断变量还是方法,其名称都以字符串形式传参;getattr和setattr也同样;
 
 
2. getattr(object, name[, default])
  获取object对象的属性的值,如果存在则返回属性值;如果不存在分为两种情况:一种是没有default参数时,会直接报错;第二种是给定了default参数,若对象本身没有name属性,则会返回给定的default值;如果给定的属性name是对象的方法,则返回的是函数对象,需要调用函数对象来获得函数的返回值;调用的话就是函数对象后面加括号,如func之于func();
  另外还需要注意,如果给定的方法func()是实例函数,则不能写getattr(A, 'func')(),因为fun()是实例函数的话,是不能用A类对象来调用的,应该写成getattr(A(), 'func')();实例函数和类函数的区别可以简单的理解一下,实例函数定义时,直接def func(self):,这样定义的函数只能是将类实例化后,用类的实例化对象来调用;而类函数定义时,需要用@classmethod来装饰,函数默认的参数一般是cls,类函数可以通过类对象来直接调用,而不需要对类进行实例化;
 
3. setattr(object, name, value)
  给object对象的name属性赋值value,如果对象原本存在给定的属性name,则setattr会更改属性的值为给定的value;如果对象原本不存在属性name,setattr会在对象中创建属性,并赋值为给定的value;
 
 
dispatch()方法如下:
 
 
判断请求方法的小写如get是否包含在View类的属性http_method_names,如果是,再获取CBV类视图中的对应的函数如get的内存地址,并赋值给handler,此时handler=get,最终返回handler的运行结果,即get(request)的运行结果。
如果请求方法不在View的类属性中或者CBV类视图里没有写入对应的请求方法处理函数,就会返回下面的http_method_not_allowed()函数的执行结果,抛出错误信息。
 
由此可以看出,如果有某个需求要指定特定的请求方法如 post,只需要在自己的类视图添加属性http_method_names = ['post']即可。
 
APIVIew
视图写法:
路由:
APIVIew类属于rest_framework下面的views.py,继承自django框架下的base.py里面的View类。
APIVIew类里也有个as_view()方法
 
APIView的as_view()中,view就等于是父类的as_view()的执行结果,也就是base.py里的view函数的内存地址了,两个view相同了。由于python一切皆对象,view.cls = cls相当于给view这个函数添加了个属性cls。此时view里的cls指的就是BooksAPIView这个类。
 
最后return csrf_exempt(view)表明只要继承了APIView,就没有了csrf认证。
禁用csrf认证可以通过装饰器完成,也可以在路由中直接调用,如FBV模式中出现path('test/',csrf_exempt(views.test))也很正常。
 
由于APIView的as_view()中也有dispatch()方法,所以APIView对View中的dispatch()方法进行了重写,使得最终view()函数调用的是重写后的dispatch()方法。
 
 1    def dispatch(self, request, *args, **kwargs):
 2         """
 3         `.dispatch()` is pretty much the same as Django's regular dispatch,
 4         but with extra hooks for startup, finalize, and exception handling.
 5         """
 6         self.args = args
 7         self.kwargs = kwargs
 8         request = self.initialize_request(request, *args, **kwargs)
 9         self.request = request
10         self.headers = self.default_response_headers  # deprecate?
11 
12         try:
13             self.initial(request, *args, **kwargs)
14 
15             # Get the appropriate handler method
16             if request.method.lower() in self.http_method_names:
17                 handler = getattr(self, request.method.lower(),
18                                   self.http_method_not_allowed)
19             else:
20                 handler = self.http_method_not_allowed
21 
22             response = handler(request, *args, **kwargs)
23 
24         except Exception as exc:
25             response = self.handle_exception(exc)
26 
27         self.response = self.finalize_response(request, response, *args, **kwargs)
28         return self.response

request = self.initialize_request(request, *args, **kwargs) 这句代码对原来的request进行了初始化封装,加入了很多东西如请求解析内容。使得现在的request不再是HttpRequest的对象,而是rest_framework/request.py模块里的Request类的对象。

 

 在Request类中,原本请求的request对象变成了类的一个受保护属性_request(不能通过from xx import xx的方式导入)。

补充:类中的私有属性__y是不能被类的对象和子类直接引用的,因为在python中,是通过改名的方式实现属性的私有的,python会在内部会将类A的私有属性__y改名为_A__y。所以使用对象a.__y找不到该属性的,而使用a._A__y是可以操作__y属性的。 这种语言特性叫做名称改写。

 

此时如果在类视图中print(request.method)会有什么结果?答案是能够正常获取到了请求的方法。为什么封装后request也能获得请求的方法?因为类Request对__getattr__方法进行了重写,当我们获取请求方法找不到时就会调用这个内建方法,而此时又重写了,在重写后的__getattr__里,又通过反射来获取到了self._request的属性,也就是原生request的method属性。

参考:https://www.cnblogs.com/wangyi0419/p/12592492.html

补充反射:

 

 问题:这里的except中的 return self.__getattribute__(attr)我没看懂什么意思,self不是不能直接调用__getattrbute__()吗?

 在类视图中打印request.data,也有输出:

data看起来像个属性,但其实是一个被@property装饰器装饰过的方法。他能返回多种格式的请求数据。

 
 

补充一下装饰器@property的作用:

第一:@property是为了让人方便的像调用属性一样去调用方法,不用带();

第二:与所定义的属性配合使用,这样可以防止属性被修改。让外部直接调用给定的方法名,而不知道该方法返回的真正的属性名。从而达到了隐藏属性名的作用,让用户进行使用的时候不能随便更改。

python默认的成员函数和成员变量都是公开的。可以通过加双下划线将属性变为私有,不过私有不绝对,也是可以通过_类名__属性名的方式访问和修改的。

 
 
 现在回到APIView中的dispatch(),此时的request已经封装好了。之后就进入三大认证模块等模块了。
 
 
 
 
 
 
 
 
 
未完待续。。。
 
 
 
 
 
原文地址:https://www.cnblogs.com/wangyi0419/p/14347882.html