DRF中帮我们在django的view类的基础上,又实现了个APIView
。
其中,request, response
,是继承了HttpRequest
和HttpResponse
django中的view
正常django中的view
,使用view
来实现增删改查的话,如下:
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
| class BookListView(View):
def get(self,request):
books = BookInfo.objects.all() book_list = [] for book in books: book_dict = { 'id': book.id, 'bititle': book.btitle, 'bpub_date': book.bpub_date, 'bread': book.bread, 'bcomment': book.bcomment, 'image': book.image.url if book.image else '' } book_list.append(book_dict) return JsonResponse(book_list,safe=False)
def post(self,request): json_str_bytes = request.body json_str = json_str_bytes.decode() book_dict = json.loads(json_str) book = BookInfo( btitle = book_dict['btitile'], bpub_date = book_dict['bpub_date'] ) book.save() json_dict = { 'id': book.id, 'bititle': book.btitle, 'bpub_date': book.bpub_date, 'bread': book.bread, 'bcomment': book.bcomment, 'image': book.image.url if book.image else '' } return JsonResponse(json_dict, status = 201)
class BookDetailView(View):
def get(self,request,pk):
try: book = BookInfo.objects.get(id = pk) except BookInfo.DoesNotExist: return HttpResponse({'message': '查询的数据不存在'},status = 404) book_dict = { 'id': book.id, 'bititle': book.btitle, 'bpub_date': book.bpub_date, 'bread': book.bread, 'bcomment': book.bcomment, 'image': book.image.url if book.image else '' } return JsonResponse(book_dict)
def put(self, request, pk):
try: book = BookInfo.objects.get(id = pk) except BookInfo.DoesNotExist: return HttpResponse({'message':'所要修改的数据不存在'},status = 404) json_str_bytes = request.body json_str = json_str_bytes.decode() book_dict = json.loads(json_str) book.btitle = book_dict['btitle'] book.bpub_date = book_dict['bpub_date'] book.save() book_dict = { 'id': book.id, 'bititle': book.btitle, 'bpub_date': book.bpub_date, 'bread': book.bread, 'bcomment': book.bcomment, 'image': book.image.url if book.image else '' } return JsonResponse(json_dict)
def delete(self, request, pk):
try: book = BookInfo.objects.get(id = pk) except BookInfo.DoesNotExist: return HttpResponse({'message':'所要删除的数据不存在'},status = 404) book.delete() return HttpResponse(status = 204)
|
DRF中的APIView
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status
class BookListAPIView(APIView): def get(self, request):
qs = BookInfo.objects.all() serializer = BookInfoModelSerializer(instance = qs, many = True) response = Response(serializer.data) return response
def post(self, request):
dataa = request.data serializer = BookInfoModelSerializer(data = dataa) serializer.is_valid(raise_exception = True) serializer.save() return Response(serializer.data, status = status.HTTP_201_CREATED) class BookDetailAPIView(APIView):
def get(self, request, pk): try: book = BookInfo.objects.get(id = pk) except BookInfo.DoesNotExist: return Response(status = status.HTTP_404_NOT_FOUND) serializer = BookInfoModelSerializer(book) return Response(serializer.data)
def put(self, request, pk):
try: book = BookInfo.objects.get(id = pk) except BookInfo.DoesNotExist: return Response(status = status.HTTP_404_NOT_FOUND) bookInfo = request.data bookSer = BookInfoModelSerializer(instance = book, data = bookInfo) bookSer.is_valid(raise_exception = True) bookSer.save() return Response(bookSer.data)
def delete(self, request,pk):
try: book = BookInfo.objects.get(id = pk) except BookInfo.DoesNotExist: return Response(status = status.HTTP_404_NOT_FOUND) book.delete() return Response(status = status.HTTP_204_NOT_CONTENT)
|
从中可以发现,APIView
做了哪些功能扩展:
APIView
类和一般的View
类有以下不同:
- 被传入到处理方法的请求不会是Django的
HttpRequest
类的实例,而是REST framework的Request
类的实例。
- 处理方法可以返回REST framework的
Response
,而不是Django的HttpRequest
。视图会管理内容协议,给响应设置正确的渲染器。
- 任何
APIException
异常都会被捕获,并且传递给合适的响应。
- 进入的请求将会经过认证,合适的权限和(或)节流检查会在请求被派发到处理方法之前运行。
使用APIView
类和使用一般的View
类非常相似,通常,进入的请求会被分发到合适处理方法比如.get()
,或者.post()
。另外,很多属性会被设定在控制API策略的各种切面的类上。
说白了,目前为止,APIView与View最大的区别是Request与Response。还有就是他可以增加对数据的认证,权限,节流检查操作(当然这里还没有展示这方面的功能)。
上面的代码中序列化与反序列化的逻辑区别,主要是因为使用了序列化器之后造成的。
从中我们又可以发现,其中的很多逻辑就是所操作的模型的不同,以及序列化器的不同,所以,我们可以抽象这两个为参数,用传参,这样就更抽象了。比如:
1 2 3 4 5 6 7 8 9 10 11 12
| class BookDetailAPIView(APIView):
def get(self, request, pk): try: book = modell.objects.get(id = pk) except modell.DoesNotExist: return Response(status = status.HTTP_404_NOT_FOUND) serializer = modelSerializerr(book) return Response(serializer.data)
|
所以,我们继承APIView
,做了个GenericAPIView
.
GenericAPIView
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
| from rest_framework.generics import GenericAPIView
class BookListGenericView(GenericAPIView):
serializer_class = BookInfoModelSerializer queryset = BookInfo.object.all()
def get(self, request): qs = self.get_queryset() serializer = self.get_serializer(qs, many = True) return Response(serialize r.data)
class BookDetailGenericView(GenericAPIView):
serializer_class = BookInfoModelSerializer queryset = BookInfo.object.all()
def get(self, request, pk): book = self.get_object() serializer = self.get_serializer(book) return Response(serializer.data) def put(self, request, pk): book = self.get_object() serializer = self.get_serializer(book, request.data) serializer.is_valid(raise_exception = True) serializer.save() return Response(serializer.data)
|
由上可知,增加的两个属性: serializer_class
和queryset
就是为上面所说,抽象而准备的.当然,目前只这样用,代码是稍微麻烦一点的。
所以,GenericAPIView是对序列化器,模型的抽象。
看上面的代码,我们发现,调用序列化器的代码的动作逻辑,指定查询集这样的动作逻辑还是重复的,所以我们将这些动作逻辑再抽象,
所以,我们基于GenericAPIView
.又引入了mixin
类,这样可以通过多重继承,实现我们需要的各种增删改查的类.
GenericAPIView + Mixin
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
| from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin,DestoryModelMixin,UpdateModelMixin
from rest_framework.generics import GenericAPIView
class BookListGenericView(CreateModelMixin,ListModelMixin,GenericAPIView): serializer_class = BookInfoModelSerializer queryset = BookInfo.objects.all()
def get(self, request): return self.list(request)
def post(self,request): return self.create(request)
class BookDetailGenericView(UpdateModelMixin,DestoryModelMixin,RetrieveModelMixin,GenericAPIView): serializer_class = BookInfoModelSerializer queryset = BookInfo.objects.all()
def get(self, request, pk): return self.retrieve(request, pk) def put(self, request, pk): return self.update(request, pk) def delete(self, request, pk): return self.destory(request, pk)
|
从上面可以发现,每一个get
,post
等方法中的动作逻辑,都被封装到了一个mixin
类中了,mixin
类是继承至objects不是继承至django的view类
至此,我们又发现了,我们总是要写get,post, put,delet
这些方法,这些方法相对于BookListGenericView
这个类而言也是固定的,那么,我们可以在BookListGenericView
的方法这一层面再去封装,就会引出我们GenericAPIView
+ Mixin
的合成类
合成类
ListAPIView
此类是封装了Mixins.ListModelMixin
和GenericAPIView
,所以这个类就相当于直接实现了BookListGenericView
中的get
方法,我们可以直接不写这个方法了都.
CreateAPIView
此类是封装了Mixins.CreateModelMixin
和GenericAPIView
,所以这个类就相当于直接实现了BookListGenericView
中的post方法,我们可以直接不写这个方法了都.
所以BookListGenericView
类直接可以写成:
1 2 3
| class BookListGenericView(CreateAPIView,ListAPIView): serializer_class = BookInfoModelSerializer queryset = BookInfo.objects.all()
|
更进一步:
ListCreateAPIView
此类是封装了ListAPIView
和CreateAPIView
.所以:
1 2 3
| class BookListGenericView(ListCreateAPIView): serializer_class = BookInfoModelSerializer queryset = BookInfo.objects.all()
|
RetrieveAPIView
此继承了RetrieveModelMixin,GenericAPIView
,所以这个类就相当于直接实现了BookDetailGenericView
中的**get
方**法,我们可以直接不写这个方法了都.
DestoryAPIView
此继承了DestoryModelMixin,GenericAPIView
,所以这个类就相当于直接实现了BookDetailGenericView
中的delete方法,我们可以直接不写这个方法了都.
UpdateAPIView
此继承了UpdateModelMixin,GenericAPIView,
所以这个类就相当于直接实现了BookDetailGenericView
中的put方法,我们可以直接不写这个方法了都.
所以BookDetailGenericView
类直接可以写成:
1 2 3
| class BookDetailGenericView(RetrieveAPIView,DestoryAPIView,UpdateAPIView): serializer_class = BookInfoModelSerializer queryset = BookInfo.objects.all()
|
更进一步:
RetrieveUpdateDestoryAPIView
此类继承了RetrieveAPIView,DestoryAPIView,UpdateAPIView
所以BookDetailGenericView
类直接可以写成:
1 2 3
| class BookDetailGenericView(RetrieveUpdateDestoryAPIView): serializer_class = BookInfoModelSerializer queryset = BookInfo.objects.all()
|
至此,视图中的逻辑就封装到不能再封装的地步了,RetrieveUpdateDestoryAPIView
和ListCreateAPIView
可以再封装吗?不可以,因为两者里面有同名不同参数的方法,比如get方法,一个参数只有两个,一个的get方法有三个参数,这在python中是冲突的,不能放在一个类中这样去定义.
当然,这种情况,我们可以通过视图集中的GenericViewSet去解决.
总结:
1.django
的view
类,继承了object
.
DRF
的APIview
类,继承了view
.
所以,python
的基类是object
.django
的基视图类是view
,drf
的基视图类是APIView
.
2.drf中的GenericAPIView
继承了APIView
drf中的Mixin
类(ListModelMixin, CreateModelMixin, RetrieveModelMixin,DestoryModelMixin,UpdateModelMixin
)继承于object
.
3.各种合成类,分别继承了GenericAPIView
与特定的Mixin类
视图集
django
的as_view
调用的dispatch
()中写死了调用get,post,delete这些方法.
视图集就重写了这里的逻辑,这样,这些方法可以重写了.
视图集中用:
list 提供一组数据,相当于列表视图中的get
retrieve提供单个数据,相当于详细视图中的get
create创建数据,相当于列表视图的post
update更新数据,相当于详细视图中的put
destory删除数据,相当于详细视图中的delete
视图集主要有两个类,一个是ViewSet
,继承至APIView
(当然,还继承了其他的).一个是GenericViewSet
,继承至GenericAPIView
(当然,还继承了其他的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| from rest_framework.viewsets import ViewSet
class BookViewSet(ViewSet): def list(self, request): qs = BookInfo.objects.all() serialzier = BookInfoModelSerializer(qs, many = True) return Response(serializer.data) def retrieve(self, request, pk): try: book = BookInfo.objects.get(id = pk) except BookInfo.DoesNotExist: return Response(status = status.HTTP_404_NOT_FOUND) serializer = BookInfoSerializer(book) return Response(serializer.data)
|
至此,这两个方法就可以写在同一个类中了.
当然,使用此视图的路由也做了相应的改变:
1 2 3 4
| urlpatterns = [ url(r'^books/$', views.BookViewSet.as_view({'get':'list'})), url(r'^books/(?P<pk>\d)/$', views.BookViewSet.as_view({'get':'retrieve'})), ]
|
当然可以用Mixin
和GenericViewSet
改写这个视图:
1 2 3 4 5 6
| from rest_framework.viewsets import GenericViewSet from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
class BookViewSet(ListModelMixin,RetrieveModelMixin,GenericViewSet): queryset = BookInfo.objects.all() serializer_class = BookInfoModelSerializer
|
当然 ReadOnlyModelViewSet
继承了ListModelMixin,RetrieveModelMixin,GenericViewSet
所以上面可以改写为:
1 2 3 4
| from rest_framework.viewsets import ReadOnlyModelViewSet class BookViewSet(ReadOnlyModelViewSet): queryset = BookInfo.objects.all() serializer_class = BookInfoModelSerializer
|
ModelViewSet
继承了ListModelMixin, CreateModelMixin, RetrieveModelMixin,DestoryModelMixin,UpdateModelMixin和GenericAPIView
所以,终极代码:
1 2 3 4 5 6 7
| from rest_framework.viewsets import ModelViewSet
class BookViewSet(ModelViewSet): queryset = BookInfo.objects.all() serializer_class = BookInfoSerializer
|
将增删改查,特定对象的增删改查,全都用一个视图类完成了.
且,路由也有简单版本:
1 2 3 4 5 6 7
| urlpatterns = [ ]
router = DefaultRouter() router.register(r'books', views.BookViewSet) urlpatterns += router.urls
|
那么如何在视图集中新增额外的行为呢?
新增额外的行为
比如想要查询最后一本书的信息,修改图书的阅读量的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class BookViewSet(ModelViewSet): def latest(self, request): book = BookInfo.objects.latest('id') serializer = self.get_serializer(book) return Response(serializer.data) def read(self, request, pk): book = self.get_object() book.bread = request.data.get('bread') book.save() serializer =self.get_serializer(book) return Response(serializer.data)
|
其中urls.py中可以这么写
1 2 3 4 5 6 7 8 9 10 11 12 13
| urlpatterns = [
url(r'^books/$', views.BookViewSet.as_view({'get':'list','post':'create'})), url(r'^books/(?P<pk>\d+)/$',views.BookViewSet.as_view({'get':'retrieve','put':'update','delete':'destory'})), url(r'^books/latest$', views.BookViewSet.as_view({'get':'latest'})), url(r'^books/(?P<pk>\d+)/read$', views.BookViewSet.as_view({'put':'read'})), ]
|
视图集中的路由器
我们在使用视图集的时候,可以直接用一个路由器DefaultRouter来自动注册基本的视图的路由
1 2 3 4 5 6 7 8 9 10
| from rest_framework.routers import DefaultRouter urlpatterns = [ ]
router = DefaultRouter() router.register(r'books', views.BookViewSet,base_name = 'bookinfo')
urlpatterns += router.urls
|
但是,此时,是不可以将额外增加的方法自动注册的,想要使用这个路由器,又想能注册额外的方法,只要使用一个装饰器就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class BookViewSet(ModelViewSet): @action(methods = ['get'], detail = False) def latest(self, request): book = BookInfo.objects.latest('id') serializer = self.get_serializer(book) return Response(serializer.data) @action(methods = ['put'], detail = True) def read(self, request, pk): book = self.get_object() book.bread = request.data.get('bread') book.save() serializer =self.get_serializer(book) return Response(serializer.data)
|
很明显,method参数是指定HTTP的请求方式,detail参数是区分是详细试图还是列表视图。