通过继承rest_framework
的APIView
,我们可以完成视图类的编写,在大多视图类的编写中,我们的行为是如此的一致。
- 获取
queryset
,生成Serializer
对象,返回data
数据 - 根据
pk
获取模型object
对象,进行更新操作 / 删除操作……
……
而rest_framework
将视图开发中的某些通用习语和模式抽象化,就是通用视图
。
通用视图
将一些标准行为固化成函数
,例如大家统一使用get_queryset
、get_object
来获取 instance
实例对象,这样一来既提高了代码可读性,且便于后续扩展。
一、GenericAPIView 使用
GenericAPIView
直接继承APIView
, 是通用视图的基础
, 它统一了视图行为
与对应的函数名称
,通用视图后续功能都是基于此类。
GenericAPIView
类属性与默认值:
queryset = None
serializer_class = None
lookup_field = 'pk'
lookup_url_kwarg = None
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
GenericAPIView
常用方法:
get_queryset() # 获取我们传入的 `queryset`
get_object() # 根据 `lookup_field`与`lookup_url_kwarg`关键字 获取指定对象
get_serializer(*args, **kwargs) # 获取序列化对象
paginate_queryset(queryset) # 从结果集中获取分页后的数据
example 1. 简单使用queryset
, serializer_class
# models.py
from django.db import models
class UserInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
# views.py
from rest_framework.generics import GenericAPIView
from rest_framework import serializers
from my_app.models import UserInfo
from rest_framework.response import Response
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = UserInfo
fields = '__all__'
class UserInfoView(GenericAPIView):
queryset = UserInfo.objects.all() # 设置`queryset`
serializer_class = UserSerializer # 设置 `serializer_class`
def get(self, request, *args, **kwargs):
queryset = self.get_queryset() # 返回传入的`queryset`
# 根据传入的`serializer_class`, 返回实例对象
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
# urls.py
from django.contrib import admin
from django.urls import path
from my_app import views
urlpatterns = [
path('admin/', admin.site.urls),
path('api/users/', views.UserInfoView.as_view()),
]
对http://127.0.0.1:8000/api/users/
发起 get
请求
[
{
"id": 1,
"username": "韩美美",
"password": "hmmhf45-s"
},
{
"id": 2,
"username": "李乐",
"password": "94#jd"
},
{
"id": 3,
"username": "王昭君",
"password": "sdf097^-sdf"
},
{
"id": 4,
"username": "李诗诗",
"password": "sdj875*"
},
{
"id": 5,
"username": "孤独求败",
"password": "sdfnk23"
}
]
example 2. 使用get_object()
获取对象
我们对单个对象进行操作的时候,通常是根据pk
来获取对象,这也是get_object()
的默认设置
# views.py
class UserInfoView(GenericAPIView):
queryset = UserInfo.objects.all()
serializer_class = UserSerializer
def get(self, request, *args, **kwargs):
obj = self.get_object()
serializer = self.get_serializer(obj)
return Response(serializer.data)
# urls.py
……
path('api/user/<int:pk>/', views.UserInfoView.as_view()),
……
访问http://127.0.0.1:8000/api/user/1/
的 get
方法:
{
"id": 1,
"username": "韩美美",
"password": "hmmhf45-s"
}
get_object()
的源码讲解:
除去类型检查等,get_object()
最主要的是执行以下一行代码:
queryset.get(*args, **kwargs)
从上面一行代码,我们可以看出:
-
queryset
划出了我们的查询结果集,如果我们传入的queryset = UserInfo.objects.exclude(pk=1)
,那么访问http://127.0.0.1:8000/api/user/1/
,就会报错"detail": "Not found."
-
-
object
根据kwargs
取值,在这里就是queryset.get(pk=1)
,kwargs
实际是根据我们的类属性lookup_field
、lookup_url_kwarg
, (默认lookup_url_kwarg
与lookup_field
相同)生成的。
-
# `filter_kwargs` 将作为`queryset.get(*args, **kwargs)`的 参数
# `self.kwargs` 就是`url` 中传入的参数字典
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
- 2.1
kwargs
的key
就是我们传入的lookup_field
值,如下:
class UserInfoView(GenericAPIView):
queryset = UserInfo.objects.all()
serializer_class = UserSerializer
lookup_field = "username"
则执行get_object()
,实际会执行 queryset.get(username=?)
- 2.2
kwargs
的值是从url
路径中获取,如以下url
path('api/user/<int:pk>/<str:name>/', views.UserInfoView.as_view()),
,我们想获取url
的name
作为kwargs
的value
,则通过lookup_url_kwarg = "name"
来指定。
example 3. 使用get_object()
配合 lookup_field
、lookup_url_kwarg
获取对象
# views.py
class UserInfoView(GenericAPIView):
queryset = UserInfo.objects.all()
serializer_class = UserSerializer
lookup_field = 'username' # 指定`key`
lookup_url_kwarg = 'name' # 指定 `value`获取
def get(self, request, *args, **kwargs):
obj = self.get_object()
serializer = self.get_serializer(obj)
return Response(serializer.data)
# urls.py
……
path('api/user/<str:name>/', views.UserInfoView.as_view()),
……
访问
http://127.0.0.1:8000/api/user/韩美美/
, 或者 http://127.0.0.1:8000/api/user/%E9%9F%A9%E7%BE%8E%E7%BE%8E/
{
"id": 1,
"username": "韩美美",
"password": "hmmhf45-s"
}
example 4. 使用pagination_class
配合 paginate_queryset
关于分页器的使用,参考本文 https://www.jianshu.com/p/6b799871fc67
from rest_framework.generics import GenericAPIView
from rest_framework import serializers
from my_app.models import UserInfo
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
# 实现自己的分页器
class MyPagePagination(PageNumberPagination):
page_size = 2 # 默认每页显示2条数据
page_size_query_param = 'size' # 设置 控制每页条目数的参数
max_page_size = 30 # 最大的条目数
# 实现序列化类
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = UserInfo
fields = '__all__'
class UserInfoView(GenericAPIView):
queryset = UserInfo.objects.all()
pagination_class = MyPagePagination
serializer_class = UserSerializer
def get(self, request, *args, **kwargs):
queryset = self.get_queryset() # 获取查询结果集
queryset = self.paginate_queryset(queryset) # 将查询结果集进行分页
serializer = self.get_serializer(queryset, many=True) # 将分页后的数据进行序列化
return Response(serializer.data)
访问http://127.0.0.1:8000/api/users/?page=1&size=3
的 get
方法
[
{
"id": 1,
"username": "韩美美",
"password": "hmmhf45-s"
},
{
"id": 2,
"username": "李乐",
"password": "94#jd"
},
{
"id": 3,
"username": "王昭君",
"password": "sdf097^-sdf"
}
]
二、GenericAPIView
的混入组件 mixin
我们通过 GenericAPIView
设置了通用行为 get_queryset
、get_object
、 get_serializer
、 paginate_queryset
、filter_queryset
这些通用行为统一了大家的代码书写方式,例如在进行 get
查询结果集时:比较通用的代码如下:
def get(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
针对上述行为,rest_framework
提供了一套已经实现的组件mixin
,供我们配合GenericAPIView
使用:
-
CreateModelMixin
实现了create
方法,创建对象 -
ListModelMixin
实现了list
方法,查询结果集 / 多个对象 -
RetrieveModelMixin
实现了retrieve
方法, 查询单个对象 -
UpdateModelMixin
实现了update
[更新对象全部信息] 与partial_update
[更新对象部分信息] -
DestroyModelMixin
实现了destroy
,删除对象
点开 ListModelMixin
的源码:
class ListModelMixin:
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
# 根据我们设置的`filter_backends` 对`queryset`进行过滤
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)
可以看出,rest_framework
提供的小组件
基本考虑到所有的可能,使用起来可以帮助我们节省更多的时间。
针对上述的 example 1
与 example 4
情形,我们现在可以换一种更好的实现方式
from rest_framework import mixins
class UserInfoView(GenericAPIView, mixins.ListModelMixin):
queryset = UserInfo.objects.all()
pagination_class = MyPagePagination
serializer_class = UserSerializer
def get(self, request, *args, **kwargs):
# ListModelMixin 已经帮我们实现所以细节
return self.list(request, *args, **kwargs)
针对上述的 example 2
,可以配合 RetrieveModelMixin
使用
from rest_framework import mixins
class UserInfoView(GenericAPIView, mixins.RetrieveModelMixin):
queryset = UserInfo.objects.all()
serializer_class = UserSerializer
def get(self, request, *args, **kwargs):
# RetrieveModelMixin 已经帮我们实现所以细节
return self.retrieve(request, *args, **kwargs)
三. ViewSetMixin
的使用
通过GenericAPIView
与 mixins
提供的CreateModelMixin
、ListModelMixin
等配合使用,我们可以不再自己去实现get
、post
、 put
、patch
、delete
方法,而是直接调用ListModelMixin
的list
方法,CreateModelMixin
的create
方法。
对于上述的请求分发控制
工作,我们可以在get
、 post
等方法中自己完成,但是对于这一工作,rest_framework
提供了一种更便捷的方式,只需要我们的View
类继承rest_framework.viewsets.ViewSetMixin
example 1. 使用ViewSetMixin
# views.py
from rest_framework.generics import GenericAPIView
from rest_framework import serializers
from my_app.models import UserInfo
from rest_framework import mixins
from rest_framework.viewsets import ViewSetMixin
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = UserInfo
fields = '__all__'
class UserInfoView(ViewSetMixin,
GenericAPIView,
mixins.RetrieveModelMixin,
mixins.CreateModelMixin,
mixins.ListModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
):
"""
UserInfoView 继承了 `retrieve`, `create`, `list`,
`update`, `partial_update`, `destroy`
"""
queryset = UserInfo.objects.all()
serializer_class = UserSerializer
# urls.py
from django.urls import path
from my_app import views
urlpatterns = [
# 将 `UserInfoView` 的`get`与`list`进行绑定
path('api/users/', views.UserInfoView.as_view({'get': 'list', 'post': 'create',})),
path('api/user/<int:pk>/', views.UserInfoView.as_view(
{
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
}
)),
]
对于上述继承,我们需要先继承ViewSetMixin
后继承GenericAPIView
等类。
这是因为ViewSetMixin
重写了as_view()
方法,所以我们需要把它放在前面,覆盖GenericAPIView
的as_view()
方法
ViewSetMixin
源码:
def as_view(cls, actions=None, **initkwargs):
……
# 如果我们传入的 actions = { 'get' : 'list' }
for method, action in actions.items():
# 获取 `View` 对象的 `list` 函数
handler = getattr(self, action)
# 将 `list` 函数 赋值给 `get`
setattr(self, method, handler)
……
四.总结
- 在
视图部分
,通过配合GenericAPIView
,mixin
混入组件,ViewSetMixin
控制分发,可以大大节省我们的时间,我们只需要传入GenericAPIView
的基础设置,其中queryset
与serializer_class
是必须项。
- 在
queryset = None # 必须项
serializer_class = None # 必须项
lookup_field = 'pk'
lookup_url_kwarg = None
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
- 在继承上述类时,需要注意先后关系,必须
ViewSetMixin
在前,GenericAPIView
在后。如果你记不住也没有关系,rest_framework
已经安排了rest_framework.viewsets.GenericViewSet
- 在继承上述类时,需要注意先后关系,必须
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
"""
这个类啥也不干,就是把继承关系弄好
"""
pass
-
rest_framework
怕我们每次写继承关系太累,还安排了两个常用的View
类
-
rest_framework.viewsets.ReadOnlyModelViewSet
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `list()` and `retrieve()` actions.
"""
pass
rest_framework.viewsets.ModelViewSet
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass
网友评论