不断的优化咱们写的程序,是每一个程序员必备的技能和职业素养,也是帮助咱们成长的很是重要的手段。git
引入
经过上一节课的学习,咱们已经经过DRF的序列化组件,设计出了符合rest规范的GET、POST接口逻辑,咱们知道操做数据库主要有增删改查几种方式,增长,查询两种接口逻辑咱们都实现完毕了,接下来,咱们继续实现剩下的两种,删除和修改。程序员
今日概要
- serializer进行put、delete及获取单条数据的接口设计
- 视图组件的使用及源码剖析
知识点复习回顾
- 昨日回顾
- RESTful api接口规范
- 混入类、多继承
- 函数的参数
在开始以前,按照惯例,咱们复习前两节课学习过的知识点、以及学习今天的新知识点所需掌握的知识点。github
知识点复习回顾一:RESTful api接口规范
第一节课的那张图你们还有印象吗?若是没有印象,请同窗们思考一下,我给你们总结的REST的最重要的一句话,那就是:url用来惟必定位资源,http请求方式用来定位用户行为。数据库
根据这句话,咱们设计了下面的RESTful api:django

上面就是关于REST规范的回顾,请牢记:url用来惟必定位资源,http请求方式用来定位用户行为。编程
知识点复习回顾二:混入类、多继承
我有一个Animal类,它包含以下方法:api
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
class Animal(object): def eat(self): print("Eat") def walk(self): print("Walk") def sleep(self): print("Sleep") def run(self): print("Run")
def flying(self): print("Flying") def wangwang(self): print("Wangwang") def miao(self): print()
class Dog(Animal):pass
class Cat(Animal):pass
class Bird(Animal):pass
|
能够看到,Dog类继承了Animal类,可是Dog并无飞行和喵的功能,因此,若是直接继承Animal会有一些问题,请同窗们思考,如何解决这个问题呢?网络
好了,其实咱们有多中方式能够解决这个问题,好比:app
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
class Animal(object): def eat(self): print("Eat")
def walk(self): print("Walk")
def sleep(self): print("Sleep")
def run(self): print("Run")
class Flying(object): def flying(self): print("Flying")
class WangWang(object): def wangwang(self): print("Wangwang")
class Miao(object): def miao(self): print()
class Dog(Animal, WangWang):pass
class Cat(Animal, Miao):pass
class Bird(Animal, Flying):pass
|
咱们将不一样的功能封装到了独立的类中,而后采用一种Mixin(混合类)的方式,其实也就是多继承,来解决这个问题,这在Python中是比较常见的解决方式,好比socketserver模块就用到了这种方式,当咱们须要线程的时候,能够继承线程类,当咱们须要进程的时候,能够继承进程类。socket
知识点复习回顾三:函数的参数
接下来,咱们一块儿回顾一下函数的参数,假设我有函数以下:
1 2 3 4 5 6 7 8
|
def func(a, b, c=1, *args, **kwargs): print(a, b, c, args, kwargs)
func(1, 2, 3, 4, 5) |
今日详细
好了,知识点的复习和补充,我们就到这里,接下来,正式开始今天的内容,首先开始设计剩下三个接口逻辑。
使用serializer进行put接口设计
根据规范,PUT接口用来定义用户对数据修改的逻辑,也就是update,它的url是这样的, 127.0.0.1/books/1/,请求方式是PUT,1表明的是具体的数据,使用户动态传递的,因此咱们的url应该是这样的:
1
|
re_path(r'books/(\d+)/$', views.BookFilterView.as_view()),
|
此时咱们应该从新定义一个视图类,由于url不同了,因此在views.py中,需新增一个视图类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
from rest_framework.views import APIView from app_serializer import BookSerializer
class BookFilterView(APIView):
def put(self, request, nid): book_obj = Book.objects.get(pk=nid)
serialized_data = BookSerializer(data=request.data, instance=book_obj)
if serialized_data.is_valid(): serialized_data.save() else: return Response(serialized_data.errors)
|
请注意,在序列化时,咱们除了传入data参数外,还需告诉序列化组件,咱们须要更新哪条数据,也就是instance,另外,咱们使用的序列化类仍是以前那个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
class BookSerializer(serializers.ModelSerializer): class Meta: model = Book
fields = ('title', 'price', 'publish', 'authors', 'author_list', 'publish_name', 'publish_city' ) extra_kwargs = { 'publish': {'write_only': True}, 'authors': {'write_only': True} }
publish_name = serializers.CharField(max_length=32, read_only=True, source='publish.name') publish_city = serializers.CharField(max_length=32, read_only=True, source='publish.city')
author_list = serializers.SerializerMethodField()
def get_author_list(self, book_obj): |
使用POSTMAN工具发送一个PUT请求修改数据: PUT http://127.0.0.1:9001/serializers/books/1。
请注意,此时会报错:RuntimeError: You called this URL via PUT, but the URL doesn’t end in a slash and you have APPEND_SLASH set. Django can’t redirect to the slash URL while maintaining PUT data. Change your form to point to 127.0.0.1:9001/serializers/books/1/ (note the trailing slash), or set APPEND_SLASH=False in your Django settings.
由于,若是是GET请求,Django的全局APPEND_SLASH参数为True,因此会在url后面加上/(若是没有),可是若是是PUT或者DELETE请求,APPEND_SLASH不会添加 / 到url末尾。而咱们上面定义的url是明确以 / 结尾的,因此,咱们应该在url后面加上反斜线 / ,或者把url修改成不以斜线结尾。
加上以后,再次发送请求修改数据:PUT http://127.0.0.1:9001/serializers/books/1/,查看数据库,发现,数据已经被修改了。
这就是PUT接口逻辑的设计,分为以下几个步骤:
- url设计:re_path(r’books/(\d+)/$’, views.BookFilterView.as_view())
- 视图类:从新定义一个视图类
- put方法:在视图类中定义一个put方法
- 序列化:在序列化的过程当中,须要传入当前修改的数据行,参数名为instance
- 序列化类:不须要修改
- url路径:请求时,发送的url必须与urls.py中定义的url彻底匹配
使用serializer进行delete接口设计
接下来,继续设计delete接口,根据规范,delete接口的url为:127.0.0.1/books/1/,请求方式是DELETE,与put是一致的,都是对用户指定的某行数据进行操做,数字1是动态的,因此咱们的url不变:
1
|
re_path(r'books/(\d+)/$', views.BookFilterView.as_view()),
|
一样的,视图类和序列化类都不须要从新定义,只须要在视图类中定义一个delete方法便可,以下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
class BookFilterView(APIView):
def delete(self, request, nid): book_obj = Book.objects.get(pk=nid).delete()
return Response("")
def put(self, request, nid): book_obj = Book.objects.get(pk=nid)
serialized_data = BookSerializer(data=request.data, instance=book_obj)
if serialized_data.is_valid(): serialized_data.save() return Response(serialized_data.data) else: return Response(serialized_data.errors)
|
用POSTMAN来试试DELETE请求:http://127.0.0.1:9001/serializers/books/53/,咱们将刚刚添加的数据删除,操做成功,一样的,请注意,请求url必须彻底匹配urls.py中定义的url。
使用serializer进行单条数据的接口设计
最后一个接口的设计,是对单条数据进行获取,根据规范url为:127.0.0.1/books/1/,请求方式为GET,根据url和前面两个接口的经验,此次仍然使用以前的视图类,由于根据REST规范,url惟必定位资源,127.0.0.1/books/1/和127.0.0.1/books/是不一样的资源,因此,咱们不能使用以前那个获取所有数据的视图类。这里确定不能重用以前的那个get方法,必须从新定义一个get方法。
urls.py不变,新增三个接口逻辑后的视图类以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
class BookFilterView(APIView): def get(self, request, nid): book_obj = Book.objects.get(pk=nid)
serialized_data = BookSerializer(book_obj, many=False)
return Response(serialized_data.data)
def delete(self, request, nid): book_obj = Book.objects.get(pk=nid).delete()
return Response("")
def put(self, request, nid): book_obj = Book.objects.get(pk=nid)
serialized_data = BookSerializer(data=request.data, instance=book_obj)
if serialized_data.is_valid(): serialized_data.save() return Response(serialized_data.data) else: return Response(serialized_data.errors)
|
many=False, 固然,也能够不传这个参数,由于默认是False。经过POSTMAN发送请求,成功。三个接口定义完成了,加上上一节课的get和post,两个视图类的接口逻辑以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
class BookView(APIView): def get(self, request): origin_books = Book.objects.all() serialized_books = BookSerializer(origin_books, many=True)
return Response(serialized_books.data)
def post(self, request): verified_data = BookSerializer(data=request.data)
if verified_data.is_valid(): book = verified_data.save() return Response(verified_data.data) else: return Response(verified_data.errors)
class BookFilterView(APIView): def get(self, request, nid): book_obj = Book.objects.get(pk=nid)
serialized_data = BookSerializer(book_obj, many=False)
return Response(serialized_data.data)
def delete(self, request, nid): book_obj = Book.objects.get(pk=nid).delete()
return Response("")
def put(self, request, nid): book_obj = Book.objects.get(pk=nid)
serialized_data = BookSerializer(data=request.data, instance=book_obj)
if serialized_data.is_valid(): serialized_data.save() return Response(serialized_data.data) else: return Response(serialized_data.errors)
|
到此为止,咱们已经经过序列化组件设计出了符合REST规范的五个经常使用接口,已经足够优秀了,可是还不够完美,如今假设,咱们有多个数据接口,好比(Book,Author,Publish…..)等数据表都须要定义相似的接口,能够预见,咱们须要重复定义相似上面的五个接口,这种方式将会致使大量的重复代码出现,显然,咱们的程序还有不少能够优化的地方。
请同窗们思考,若是是你,你将会如何进行优化呢?
好了,同窗们,结合刚刚上课是给你们回顾的混合类和多继承,咱们是否可使用下面的方式呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
class GetAllData(): def get(self, request):pass
class GetOneData(): def get(self, request, nid):pass class DeleteOneData(): def delete(self, request, nid):pass class UpdateOneData(): def put(self, request, nid):pass class CreateData(): def post(self, request):pass
class BookView(APIView, GetAllData, CreateData):pass
class BookFilterView(APIView, GetOneData, DeleteOneData, UpdateOneData):pass
|
将每一个接口都写到独立的类中,而后使用多继承,或者成为mixin的这种方式,就能够对咱们的程序进行优化,mixin的方式很是常见,在学网络编程的时候,若是你看过socketserver源码,就会发现,socketserver中就有对mixin的实现,即,假设咱们须要进程的时候,咱们继承进程类,若是咱们须要线程的时候,咱们继承线程类便可。
接下来,咱们一块儿来看看DRF是如何作的,其余,它的解决方式与咱们上面的方式的思路是同样的。
使用mixin优化接口逻辑
mixin的使用方式介绍
urls.py有些区别:
1 2 3 4 5 6 7 8
|
from django.urls import re_path
from mixiner import views
urlpatterns = [ re_path(r'books/$', views.BookView.as_view()), re_path(r'books/(?P<pk>\d+)/$', views.BookFilterView.as_view()), ]
|
views.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
|
DRF将这五个接口定义在不一样的ModelMixin中,使用步骤以下:
- 导入ModelMixin
- 视图类继承所需的ModelMix
- 再也不继承APIView,须要继承generics.GenericAPIView
- 必须包含两个类变量:queryset,serializer_class
- 接口中不须要定义任何逻辑操做,一切交给mixin
- 每一个接口的返回值不一样,对应关系:{“get”: “list”, “delete”: “destroy”, “put”: “update”, “get”: “retrieve”, “post”: “create”}
其中有两个get方法,可是分属于不一样的视图类,请注意在url中的不一样点,由于咱们统一给的都是QuerySet,因此,须要经过传入一个名为pk的命名参数,告诉视图组件,用户须要操做的具体数据。
mixin源码剖析
到底它是如何作的呢?咱们来简单剖析一下源码:
- Django程序启动,开始初始化,读取urls.py, 读取settings, 读取视图类
- 执行as_views(), BookView没有,须要到父类中找
- 几个ModelMixin也没有,GenericAPIView中没有,继续到GenericAPIView(APIView)中找
- 找到了,而且与以前的逻辑是同样的,同时咱们发现GenericAPIView中定义了查找queryset和serializer_class类的方法
- as_view()方法返回从新封装的视图函数,开始创建url和视图函数之间的映射关系
- 等待用户请求
- 接收到用户请求,根据url找到视图函数
- 执行视图函数的dispatch方法(由于视图函数的返回值是:return self.dispatch()
- dispatch分发请求,查找到视图类的五个方法中的某个
- 开始执行,好比post请求,返回:self.create(),视图类自己没有,则会到父类中查找
- 最后在CreateModelMixin中查找
- 执行create()方法,获取queryset和serializer_class
- 返回数据
在对单条数据进行操做的几个方法里面,好比retrieve,会执行get_object()方法,该方法会根据lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field来查找操做对象,咱们能够经过修改self.lookup_url_kwarg变量名来自定义参数。
好了,以上就是mixin的源码剖析。
使用view优化接口逻辑
view的使用方式介绍
看似已经优化的很是完美了,可是,在一个对性能要求极高的项目里面,咱们的程序还能够继续优化,还须要继续优化,不断的优化咱们写的程序,是每一个程序员必备的技能,也是帮助咱们成长的很是重要的手段。一样的思路,一样的方式,咱们能够将多个接口封装到一个功能类中,请看下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
|
是否是很是简单,你想到了吗?
使用viewset优化接口逻辑
这样就结束了吗?哈哈哈,仍是那句话,看似已经优化的很是完美了,可是,在一个对性能要求极高的项目里面,咱们的程序还能够继续优化,还须要继续优化,不断的优化咱们写的程序,是每一个程序员必备的技能,也是帮助咱们成长的很是重要的手段。
viewset的使用方式介绍
urls.py有变化哦:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
from django.urls import re_path
from viewsetter import views
urlpatterns = [ re_path(r'books/$', views.BookView.as_view({ 'get': 'list', 'post': 'create' })), re_path(r'books/(?P<pk>\d+)/$', views.BookView.as_view({ 'get': 'retrieve', 'put': 'update', 'delete': 'destroy' })), ]
|
咱们给as_view()方法传递了参数,这就是最神奇的地方。
使用方式很是简单,接下来,咱们直接看源码。
viewset源码剖析
- Django程序启动,开始初始化,读取urls.py, 读取settings, 读取视图类
- 执行as_views(), BookView没有,须要到父类(ModelViewSet)中找
- ModelViewSet继承了mixins的几个ModelMixin和GenericViewSet,显然ModelMixin也没有,只有GenericViewSet中有
- GenericViewSet没有任何代码,只继承了ViewSetMixin和generics.GenericAPIView(这个咱们已经认识了)
- 继续去ViewSetMixin中查找,找到了as_view类方法,在从新封装view函数的过程当中,有一个self.action_map = actions
- 这个actions就是咱们给as_view()传递的参数
- 绑定url和视图函数(actions)之间的映射关系
- 等待用户请求
- 接收到用户请求,根据url找到视图函数
- 执行视图函数的dispatch方法(由于视图函数的返回值是:return self.dispatch()
- dispatch分发请求,查找到视图类的五个方法中的某个
- 开始执行,好比post请求,返回:self.create(),视图类自己没有,则会到父类中查找
- 最后在CreateModelMixin中查找
- 执行create()方法,获取queryset和serializer_class
- 返回数据
这就是viewset的优化方案,整个优化方案最重要的地方就是urls.py中咱们传入的参数,而后对参数进行映射关系绑定。
今天的所有内容到此就结束了。
今日总结
- serializer进行put、delete及获取单条数据的接口设计
- 视图组件的使用及源码剖析
转自:pizzali