前言:
Django REST framwork 提供的视图的主要用途:
- 控制序列化器的执行(检验、保存、转换数据)
- 控制数据库查询的执行
虽说视图也可以基于FBV模式来进行开发,但是使用CBV能够让我们避免很多重复代码,类具备继承、封装、多态等等,而DRF也给我们提供了非常丰富的视图组件,一起来了解一下吧!
一、视图基类
DRF提供了众多通用的视图基类与拓展类,以供我们简化视图的编写。
1.1 APIView基类
APIView
是DRF提供的所有视图的基类(父类),而APIView则继承自Django的View
作为父类
APIView
与View
的不同之处在于:
- 传入到视图方法中的是REST framework的Request对象,而不是Django的HttpRequeset对象;
- 视图方法可以返回REST framework的Response对象,视图会为响应数据设置(render)符合前端要求的格式;
- 任何APIException异常都会被捕获到,并且处理成合适的响应信息;
- 在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制。
支持定义的类属性(部分):
属性名 | 定义方式 | 作用 |
---|---|---|
authentication_classes | 列表或元祖 | 权限检查类 |
permissoin_classes | 列表或元祖 | 列表或元祖 |
throttle_classes | 列表或元祖 | 流量控制类 |
在APIView
中仍以常规的类视图定义方法来实现get()
、post()
或者其它方法来处理请求;
例如:
from rest_framework.views import APIView
from rest_framework.response import Response
from app01 import model
from app01 import serializer
class BookView(APIView):
def get(self,request):
book_list = model.Book.objects.all()
ser = serializer.BookSerializer(instance=book_list,many=True)
return Response(ser.data)
这也就是我们平常所编写的,获取数据、传入序列化器、得到序列化后的内容、
1.2 GenericAPIView通用视图基类
from rest_framework.generics import GenericAPIView
继承自APIVIew,主要增加了操作序列化器和数据库查询的方法,作用是为下面Mixin扩展类的执行提供方法支持。通常在使用时,可搭配一个或多个Mixin扩展类。
需要使用的类属性:
-
queryset:指明使用的数据查询集
-
serializer_class:指明视图使用的序列化器
from rest_framework.generics import GenericAPIView class BooView(GenericAPIView): queryset = models.Student.objects.all() serializer_class = serializer.StudentSerializer
内部常用方法:
get_serializer_class(self)
-
当出现一个视图类中调用多个序列化器时,那么可以通过条件判断在get_serializer_class方法中通过返回不同的序列化器类名就可以让视图方法执行不同的序列化器对象了。
-
返回序列化器类,默认返回serializer_class
一般不会自动调用
get_queryset():获取查询集(等同于获取queryset类属性的值)
class StudentView(GenericAPIView):
queryset = models.Student.objects.all()
serializer_class = serializer.StudentSerializer
def get(self,request):
qs = self.get_queryset()
get_serializer(self, args, *kwargs)
返回序列化器对象,主要用来提供给Mixin扩展类使用,如果我们在视图中想要获取序列化器对象,也可以直接调用此方法。
注意:该方法在提供序列化器对象的时候,会向序列化器对象的context属性补充三个数据:request、format、view,这三个数据对象可以在定义序列化器时使用。
- request 当前视图的请求对象
- view 当前请求的类视图对象
- format 当前请求期望返回的数据格式
class StudentView(GenericAPIView):
queryset = models.Student.objects.all()
serializer_class = serializer.StudentSerializer
def get(self,request):
qs = self.get_queryset()
ser = self.get_serializer(instance=qs,many=True) # 和正常调用序列化器一样
return MyResponse(msg=ser.data)
get_object():返回详情视图所需的模型类数据对象(方法必须要有一个形参名为pk),若详情访问的模型类对象不存在,会返回404。
class StudentDetailView(GenericAPIView):
queryset = models.Student.objects.all()
serializer_class = serializer.StudentSerializer
def get(self,request,*args,**kwargs): # kwargs内必须有一个key名为pk
qs = self.get_object() # 获取某个对象,它的主键值与kwargs里的pk值相等
ser = self.get_serializer(instance=qs) # 传入序列化器
return MyResponse(msg=ser.data)
其他可以设置的属性
- pagination_class 指明分页控制类
- filter_backends 指明过滤控制后端
整体视图代码:
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from app01 import serializer
from app01 import models
class StudentView(GenericAPIView):
queryset = models.Student.objects.all() # 指定序列化的数据(GenericAPIView内部会调用该方法)
serializer_class = serializer.StudentSerializer # 指定序列化器
def get(self, request):
qs = self.get_queryset() # 获取需要序列化的数据(等同于获取了queryset的值)
ser = self.get_serializer(qs, many=True) # 将序列化数据传入序列化器,得到序列化器的对象
return Response(ser.data)
def post(self, request):
ser = self.get_serializer(data=request.data)
if ser.is_valid():
ser.save()
return Response(ser.data)
else:
return Response(ser.error)
class StudentDetailView(GenericAPIView):
queryset = models.Student.objects.all()
serializer_class = serializer.StudentSerializer
def get(self, request, *args,**kwargs):
# 会从形参中获取一个名为pk的形参值,然后再拿其在上面querset属性里面找到符合要求的对象
obj = self.get_object()
if obj:
ser = self.get_serializer(instance=obj)
return Response(ser.data)
else:
return Response('查询的数据不存在')
def put(self, request, *args,**kwargs):
obj = self.get_object()
if obj:
ser = self.get_serializer(instance=obj,data=request.data)
if ser.is_valid():
ser.save()
return Response(ser.data)
else:
return Response(ser.errors)
else:
return Response('修改的数据不存在')
def delete(self, request, pk):
obj = models.Student.objects.filter(pk=pk)
if obj:
obj.delete()
return Response('')
else:
return Response('修改的数据不存在')
序列化器类:
from rest_framework import serializers
from app01 import models
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = models.Student # 指明参照的模型类
fields = '__all__' # 包含模型类的所有字段
# 为字段添加一些参数
extra_kwargs = {
'create_date': {'required': False, 'read_only': True}
}
可能暂时没有看出GenericAPIView这个类带给我们的优势,因为我们这个GenericAPIView是作为基类使用的,需要搭配Mixin拓展类
一起使用。
二、GenericAPIView的视图拓展类
提供了几种后端视图(对数据资源进行曾删改查)处理流程的实现,如果需要编写的视图属于这五种,则视图可以通过继承相应的扩展类来复用代码,减少自己编写的代码量。
这五个扩展类需要搭配GenericAPIView父类,因为五个扩展类的实现需要调用GenericAPIView提供的序列化器与数据库查询的方法。
在实例开始前,准备序列化器:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = models.Book # 指明参照的模型类
fields = '__all__' # 包含模型类的所有字段
extra_kwargs = {
'create_date':{'required':False,'read_only':True}
}
路由配置:
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('book/', views.BookView.as_view()),
path('book/<int:pk>/', views.BookDetailView.as_view()),
# 形参名必须为pk,因为GenericAPIView类内部需要(用于对单个对象操作)
]
2.1 ListModelMixin
列表视图扩展类,提供list(request, *args, **kwargs)
方法快速实现列表视图,返回200状态码。
该Mixin的list方法会对数据进行过滤和分页。
源代码:
class ListModelMixin:
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
举例说明作用:
未调用list方法前:
from rest_framework.generics import GenericAPIView
class StudentView(GenericAPIView):
queryset = models.Book.objects.all() # 指定序列化的数据(GenericAPIView内部会调用该方法)
serializer_class = serializer.BookSerializer # 指定序列化器
def get(self, request):
qs = self.get_queryset() # 获取需要序列化的数据(等同于获取了queryset的值)
ser = self.get_serializer(qs, many=True) # 将序列化数据传入序列化器,得到序列化器的对象
return Response(ser.data)
调用list方法后:
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin
class StudentView(GenericAPIView,ListModelMixin):
queryset = models.Book.objects.all() # 指定序列化的数据(GenericAPIView内部会调用该方法)
serializer_class = serializer.BookSerializer # 指定序列化器
def get(self, request,*args,**kwargs):
# 内部list方法会将需要序列化的数据传给序列化器,然后返回序列化器对象data数据
return self.list(request,*args,**kwargs)
这两种方式实现的效果相同,但是明显下面代码会少一些。
2.2 CreateModelMixin
创建视图扩展类,提供create(request, *args, **kwargs)
方法快速实现创建资源的视图,成功返回201状态码。
如果序列化器对前端发送的数据校验失败,则返回400错误。
源代码
class CreateModelMixin:
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
# 传递前端发送过来的数据,获取序列化器对象
serializer = self.get_serializer(data=request.data)
# 进行校验,如果校验失败则直接抛出异常
serializer.is_valid(raise_exception=True)
# 调用下面的方法,将序列化器对象传入
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
# 调用了序列化器的save方法,由于我们序列化器继承的是ModelSerializer,所以不需要重写create方法就能将数据新增。
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
使用方式:
from rest_framework.mixins import ListModelMixin,CreateModelMixin
class BookView(GenericAPIView,ListModelMixin,CreateModelMixin):
queryset = models.Book.objects.all()
serializer_class = serializer.BookSerialize
def get(self, request,*args,**kwargs):
return self.list(request,*args,**kwargs)
def post(self, request,*args,**kwargs):
# 调用CreateModelMixin类内部的create方法进行保存数据的操作
return self.create(request,*args,**kwargs)
使用效果:
此时我们的代码已经开始越写越少了,因为继承的类已经帮助我们调用了正常需要调用的代码,此后新增直接调用即可,但也不排除少数情况需要我们重写create方法。
2.3 RetrieveModelMixin
详情视图扩展类,提供retrieve(request, *args, **kwargs)
方法,可以快速实现返回一个存在的数据对象。
如果存在,返回200, 否则返回404。
源代码:
class RetrieveModelMixin:
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
本质就是在kwargs
获取一个key为pk的关键字的值,然后在queryset
查询集内获取pk值相同的对象传入序列化器,响应序列化后的数据。
使用方式:
from rest_framework.mixins import RetrieveModelMixin
class BookDetailView(GenericAPIView,RetrieveModelMixin):
queryset = models.Book.objects.all()
serializer_class = serializer.BookSerializer
def get(self, request, *args,**kwargs):
# retrieve方法本质上还是调用了self.get_object方法拿到对象传给序列化器,然后返回序列化器的data数据
return self.retrieve(request,*args,**kwargs) # RetrieveModelMixin类内部方法
使用效果:
2.4 UpdateModelMixin
更新视图扩展类,提供update(request, *args, **kwargs)
方法,可以快速实现更新一个存在的数据对象。
同时也提供partial_update(request, *args, **kwargs)
方法,可以实现局部更新。
成功返回200,序列化器校验数据失败时,返回400错误。
源代码
class UpdateModelMixin:
"""
Update a model instance.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
# 获取需要更改数据的对象
instance = self.get_object()
# 将对象、数据传入序列化器,
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
# 调用下面,执行更新操作
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data)
def perform_update(self, serializer):
# 序列化器接收了instance对象,执行save后会执行内部的update方法。
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
使用方式:
from rest_framework.mixins import UpdateModelMixin
class BookDetailView(GenericAPIView,UpdateModelMixin):
queryset = models.Book.objects.all()
serializer_class = serializer.BookSerializer
def put(self, request, *args,**kwargs):
# 内部执行原理:先获取某个对象,然后一并将request.data数据传给序列化器,拿到序列化器对象的data数据
return self.update(request,*args,**kwargs) # UpdateModelMixin类内部的方法
使用效果:
2.5 DestroyModelMixin
删除视图扩展类,提供destroy(request, *args, **kwargs)
方法,可以快速实现删除一个存在的数据对象。
成功返回204,不存在返回404。
源代码:
class DestroyModelMixin:
"""
Destroy a model instance.
"""
def destroy(self, request, *args, **kwargs):
# 获取需要删除的对象
instance = self.get_object()
# 执行删除操作
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance):
instance.delete()
使用方式:
from rest_framework.mixins import DestroyModelMixin
class BookDetailView(GenericAPIView,DestroyModelMixin):
queryset = models.Book.objects.all()
serializer_class = serializer.BookSerializer
def delete(self, request, *args,**kwargs):
return self.destroy(request,**kwargs) # DestroyModelMixin类内部方法
使用效果:
此时我们整体代码相对平常编写的视图已经减少了很多,并且能够正常实现功能。下面了解到的将会使我们的代码更加简洁、并保证了接口使用
三、GenericAPIView的视图子类
GenericAPIView提供了5个视图拓展类以及9个视图子类,上面5个拓展类已经介绍了,下面来介绍视图子类
GenericAPIView的视图子类是依赖于视图拓展类的,视图子类内部本质就继承了视图拓展,而且它内部为我们放在了对于的方法里面,所以我们只要使用了视图子类,便无需再写执行请求的方法。
我们先写完实例,再来一一介绍视图子类:
3.1 ListCreateAPIView
执行查询所有数据与新增数据,我们可以继承ListCreateAPIView这个视图子类
源代码:
# 继承了GenericAPIView视图基类、以及2个视图拓展类
class ListCreateAPIView(mixins.ListModelMixin,mixins.CreateModelMixin,GenericAPIView):
"""
Concrete view for listing a queryset or creating a model instance.
"""
# 与我们使用视图拓展类时代码相同,不过省略了我们定义请求方法的步骤。
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
使用方式:
from rest_framework.generics import ListCreateAPIView
class BookView(ListCreateAPIView):
# ListCreateAPIView类里面具备了get方法与post方法,请求来的时候回调用该类的get或post请求
# 而该类的内部又继承了GenericAPIView、ListModelMixin、CreateModelMixin
queryset = models.Book.objects.all()
serializer_class = serializer.BookSerializer
使用效果:下面测试的是GET请求,而POST请求也是可以处理的,和我们上面效果相同。
3.2 RetrieveUpdateDestroyAPIView
该类内部具备了get(查询单个对象数据)、put、patch、delete方法。同样也对应继承了3个拓展类
源代码:
class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
GenericAPIView):
"""
Concrete view for retrieving, updating or deleting a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
通过内部具备了处理请求的方法,然后又继承了拓展类,所以我们只需要填写3行的固定代码即可实现这3个接口的使用
from rest_framework.generics import RetrieveUpdateDestroyAPIView
class BookDetailView(RetrieveUpdateDestroyAPIView):
queryset = models.Book.objects.all()
serializer_class = serializer.BookSerializer
使用效果:
上面的两种方式就集成了5个接口的使用,且不需要我们手动定义处理请求的方法,而如果我们不想一下开启这么多接口,那么不妨使用下面的子类
from rest_framework.generics import ListAPIView
:具备了查询所有数据的get方法from rest_framework.generics import CreateAPIView
:具备新增数据的post方法from rest_framework.generics import RetrieveAPIView
:内部具备了查询单个对象的get方法from rest_framework.generics import UpdateAPIView
:内部具备了修改单个对象的put方法from rest_framework.generics import DestroyAPIView
:具备了删除单个对象的delete方法from rest_framework.generics import RetrieveUpdateAPIView
:具备了查询、修改单个对象的get、put方法from rest_framework.generics import RetrieveDestroyAPIView
:具备了查询、删除单个对象的get、delete方法
总结:
综合上面我们使用过的两种方法、加上这些总共9个视图子类,而它们内部全部需要继承GenericAPIView作为基类,然后又继承了一些扩展类放在对应的请求方法里面,实现了我们只需要继承一些子类就能实现更多接口。
但是由于get请求无法区分是查询全部内容还是单个内容,所以我们不得不拆分成两个类来处理,下面了解到的视图集可以很好的解决这个问题
四、视图集父类
在开始了解视图集之前,我们应该先了解一些视图集的父类
4.1 ViewSet
继承自APIView与ViewSetMixin,作用也与APIView基本类似,提供了身份认证、权限校验、流量管理等。
ViewSet主要通过继承ViewSetMixin来实现在调用as_view()时传入字典,如:{‘get’:’list’}
的映射处理工作。这表示:将get请求交给视图里面的list方法来处理
在ViewSet中,没有提供任何方法,需要我们自己手动定义方法。
class BookInfoViewSet(viewsets.ViewSet):
def list(self, request):
books = BookInfo.objects.all()
serializer = BookInfoSerializer(books, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
try:
books = BookInfo.objects.get(id=pk)
except BookInfo.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = BookInfoSerializer(books)
return Response(serializer.data)
路由应该这样配置
urlpatterns = [
path('books/', BookInfoViewSet.as_view({'get':'list'}),
path('books/<int:pk>/', BookInfoViewSet.as_view({'get': 'retrieve'})
]
4.2 GenericViewSet
使用ViewSet通常并不方便,因为list、retrieve、create、update、destory
等方法都需要自己编写,而这些方法与前面讲过的Mixin扩展类提供的方法同名,所以我们可以通过继承Mixin扩展类来复用这些方法而无需自己编写。但是Mixin扩展类依赖与GenericAPIView,所以还需要继承GenericAPIView。
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin
class StudentView(GenericViewSet,ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin):
queryset = Student.objects.all()
serializer_class = StudentSerializer
路由的定义
urlpatterns = [
path("students/", views.StudentView.as_view({"get": "list", "post": "create"})),
path("students/<int:pk>/", views.StudentView.as_view({"get": "retrieve","put":"update","delete":"destroy"})),
]
此时虽然可以一个类实现5个接口,但是需要我们手动继承的类未免太多,所以我们接下来会了解到一种只需要继承一个类,实现5个接口,并且我们只需要定义一个类即可。
五、视图集子类
5.1 ModelViewSet
继承自GenericViewSet,同时也继承了ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin
。
视图
from rest_framework.viewsets import ModelViewSet
class BookView(ModelViewSet):
queryset = models.Book.objects.all()
serializer_class = serializer.BookSerializer
ModelViewSet最主要的就是继承了GenericViewSet而它又继承了ViewSetMixin类。那么只要我们继承了ViewSetMixin类,那么路由调用视图类的方式就要发生改变,上面两个实例就可以说明
如下:
urlpatterns = [
path("books/", views.BookView.as_view({"get": "list", "post": "create"})),
path("books/<int:pk>/", views.BookView.as_view({"get": "retrieve","put":"update","delete":"destroy"})),
]
5.2 ReadOnlyModelViewSet
只读的视图集,它具备了查询数据的接口,单个对象查询、所有对象查询。
继承自GenericViewSet,同时包括了:ListModelMixin、RetrieveModelMixin
。
5.3 在视图集中自定义方法
在视图集中,除了上述默认的方法动作外,还可以添加自定义动作。
from rest_framework.viewsets import ModelViewSet
class StudentView(ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentSerializer
def login(self,request):
"""学生登录功能"""
return Response({"message":"登录成功"})
路由配置
urlpatterns = [
path("students/", views.StudentView.as_view({"get": "list", "post": "create"})),
path("students/<int:pk>/", views.StudentView.as_view({"get": "retrieve","put":"update","delete":"destroy"})),
path("stu/login/",views.StudentView.as_view({"get":"login"}))
]
匹配上路由以后,如果是get请求则交给视图集里面的login方法来处理。
如果本文对您有帮助,别忘一键3连,您的支持就是笔者最大的鼓励,感谢阅读!
技术小白记录学习过程,有错误或不解的地方请指出,如果这篇文章对你有所帮助请
点赞 收藏+关注
子夜期待您的关注,谢谢支持!