DRF的视图

DRF中帮我们在django的view类的基础上,又实现了个APIView

其中,request, response,是继承了HttpRequestHttpResponse

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):
#查询所有图书的接口

#1.查询出所有图书模型
books = BookInfo.objects.all()
#2.遍历返回的查询集,取出里面的每个书籍模型对象,把模型对象转换成字典
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)
#3.响应
return JsonResponse(book_list,safe=False)

def post(self,request):
#新增图书的接口

#=====================
#1.获取前端传过来的数据(json) request.body
json_str_bytes = request.body
#2.把bytes类型的json字符串转换成json_str
json_str = json_str_bytes.decode()
#3.利用json.loads将json字符串转换成json(字典、列表)
book_dict = json.loads(json_str)
#4.创建模型对象并保存(把字典转换成模型并存储)
book = BookInfo(
btitle = book_dict['btitile'],
bpub_date = book_dict['bpub_date']
)
book.save()
#====================

#5.将新增的模型转换成字典
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 ''
}
#6.响应(将新增的数据再响应回去,201)
return JsonResponse(json_dict, status = 201)

class BookDetailView(View):

def get(self,request,pk):
#查询指定的图书的接口

#1.获取出指定的pk的那个模型对象
try:
book = BookInfo.objects.get(id = pk)
except BookInfo.DoesNotExist:
return HttpResponse({'message': '查询的数据不存在'},status = 404)
#2.模型对象转字典
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 ''
}
#3.响应
return JsonResponse(book_dict)


def put(self, request, pk):
#修改指定的图书的接口

#1.先查询要修改的模型对象
try:
book = BookInfo.objects.get(id = pk)
except BookInfo.DoesNotExist:
return HttpResponse({'message':'所要修改的数据不存在'},status = 404)
#2.获取前端传入的新数据(将数据转换成字典)
json_str_bytes = request.body
json_str = json_str_bytes.decode()
book_dict = json.loads(json_str)
#上面的三行代码等价于: book_dict = json.loads(request.body.decode())
#3.重新给指定的属性赋值
book.btitle = book_dict['btitle']
book.bpub_date = book_dict['bpub_date']
#...
#4.调用save方法进行修改操作
book.save()
#5.将修改后的模型再转换成字典
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 ''
}
#6.响应
return JsonResponse(json_dict)


def delete(self, request, pk):
#删除指定的图书

#1.获取到想要删除的模型对象
try:
book = BookInfo.objects.get(id = pk)
except BookInfo.DoesNotExist:
return HttpResponse({'message':'所要删除的数据不存在'},status = 404)
#2.删除模型对象
book.delete() #真正从数据库中删除
# book.is_delete = True 这是逻辑上的删除,数据库中还有其信息
# book.save()
#3.响应
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
#serializer 是序列化后,但是没有渲染的数据
#调用Response(serializer.data)之后,就是渲染后的数据,
#当然,response.data也可以显示未渲染的数据
#response.content显示渲染后的数据,当然,其中django会调用render方法,之后才会有content也即渲染后的数据

def post(self, request):
#新增

#获取前端传入的数据
dataa = request.data#request不再是django的request,用法更简单了,这里拿到手就是一个json数据
#创建序列化器,进行反序列化(反序列化的过程中有序列化)
serializer = BookInfoModelSerializer(data = dataa)
#调用序列化器中的is_valid()方法校验
serializer.is_valid(raise_exception = True)
#调用序列化器的save方法进行执行create方法
serializer.save()
#响应(用的序列化)
return Response(serializer.data, status = status.HTTP_201_CREATED)

class BookDetailAPIView(APIView):

def get(self, request, pk):
#查询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):
#修改

#查询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)
#调用save方法,此方法中又调用update方法
bookSer.save()
#响应
return Response(bookSer.data) #默认返回的是200状态码

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):
#查询pk指定的模型对象
try:
book = modell.objects.get(id = pk) #将BookInfo抽象为modell
except modell.DoesNotExist:#同理
return Response(status = status.HTTP_404_NOT_FOUND)
#创建序列化器进行序列化
serializer = modelSerializerr(book) #将BookInfoModelSerializer抽象为modelSerializer
#响应
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):

#使用GenericAPIView要指定序列化器和指定查询集(数据来源,也即是哪个模型的哪些数据)
serializer_class = BookInfoModelSerializer
queryset = BookInfo.object.all()

def get(self, request):
#分别用这两个方法,获取到serialzier_class 和queryset
qs = self.get_queryset()
serializer = self.get_serializer(qs, many = True)#对这个查询集使用序列化器
#get_serializer方法是返回一个序列化器对象.为什么还要用get_serializer方法去调用序列化器,是因为,我们可以在方法中再增加操作序列化器的逻辑,比如在某种情况下用a序列化器,在满足另一种情况下用b序列化器,这样更灵活了.
return Response(serialize r.data)



class BookDetailGenericView(GenericAPIView):

#使用GenericAPIView要指定序列化器和指定查询集(数据来源,也即是哪个模型的哪些数据)
serializer_class = BookInfoModelSerializer
queryset = BookInfo.object.all()

def get(self, request, pk):
#分别用这两个方法,获取到serialzier_class 和pk指定的哪个对象的数据
book = self.get_object() #直接用get_object方法获取到pk的对象的数据,pk都没有作为参数传进去,因为这个方法的内部帮我们传参了
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_classqueryset就是为上面所说,抽象而准备的.当然,目前只这样用,代码是稍微麻烦一点的。

所以,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.ListModelMixinGenericAPIView,所以这个类就相当于直接实现了BookListGenericViewget方法,我们可以直接不写这个方法了都.

CreateAPIView

此类是封装了Mixins.CreateModelMixinGenericAPIView,所以这个类就相当于直接实现了BookListGenericView的post方法,我们可以直接不写这个方法了都.

所以BookListGenericView类直接可以写成:

1
2
3
class BookListGenericView(CreateAPIView,ListAPIView):
serializer_class = BookInfoModelSerializer
queryset = BookInfo.objects.all()

更进一步:

ListCreateAPIView

此类是封装了ListAPIViewCreateAPIView.所以:

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()

至此,视图中的逻辑就封装到不能再封装的地步了,RetrieveUpdateDestoryAPIViewListCreateAPIView可以再封装吗?不可以,因为两者里面有同名不同参数的方法,比如get方法,一个参数只有两个,一个的get方法有三个参数,这在python中是冲突的,不能放在一个类中这样去定义.

当然,这种情况,我们可以通过视图集中的GenericViewSet去解决.

总结:

1.djangoview类,继承了object.

DRFAPIview类,继承了view.

所以,python的基类是object.django的基视图类是view,drf的基视图类是APIView.

2.drf中的GenericAPIView继承了APIViewdrf中的Mixin类(ListModelMixin, CreateModelMixin, RetrieveModelMixin,DestoryModelMixin,UpdateModelMixin)继承于object.

3.各种合成类,分别继承了GenericAPIView与特定的Mixin类

视图集

djangoas_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): #这个方法对应了原来的列表视图中的get方法
qs = BookInfo.objects.all()
serialzier = BookInfoModelSerializer(qs, many = True)
return Response(serializer.data)

def retrieve(self, request, pk):#这个方法对应了原来的详细视图中的get方法
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'})), #这个字典,左值为Http的请求方式,是GET还是POST请求
url(r'^books/(?P<pk>\d)/$', views.BookViewSet.as_view({'get''retrieve'})),
]

当然可以用MixinGenericViewSet改写这个视图:

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):
#1.指定查询集
queryset = BookInfo.objects.all()
#2.定义序列化器
serializer_class = BookInfoSerializer

将增删改查,特定对象的增删改查,全都用一个视图类完成了.

且,路由也有简单版本:

1
2
3
4
5
6
7
urlpatterns = [

]

router = DefaultRouter()#创建一个路由器,也即创建这个类的一个实例
router.register(r'books', views.BookViewSet)#注册路由
urlpatterns += router.urls#将生成好的路由拼接到urlpatterns

那么如何在视图集中新增额外的行为呢?

新增额外的行为

比如想要查询最后一本书的信息,修改图书的阅读量的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#写在views.py中的BookViewSet类中
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.BookListView.as_view()),
#url(r'^books/(?P<pk>\d)/$', views.BookListView.as_view()),

#使用视图集的情况
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'})),
#根据REST风格,详细视图时,新增其他动作,就在pk之后,增加动作,以做为路由
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 = [

]

#初体验:下面的逻辑是使用drf中的路由的情况
router = DefaultRouter()#创建一个路由器,也即创建这个类的一个实例
router.register(r'books', views.BookViewSet,base_name = 'bookinfo')#注册路由,books是路由的前缀
#base_name 默认是可以不用传参数的,如果要写,写对应的模型名的小写就行
urlpatterns += router.urls#将生成好的路由拼接到urlpatterns

但是,此时,是不可以将额外增加的方法自动注册的,想要使用这个路由器,又想能注册额外的方法,只要使用一个装饰器就可以了。

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参数是区分是详细试图还是列表视图。