一、Generics:工具视图

generics 是工具视图,可以实现极简化接口编写操作。

工具视图都是 GenericAPIView 的子类,不同的子类继承不同工具类,重写请求方法。
mark

1.群查与单增:ListCreateAPIView

查看源码
# 继承了视图基类 GenericAPIView,工具类 ListModelMixin,CreateModelMixin实现群查和单增
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):
# 继承了视图基类 GenericAPIView,工具类 ListModelMixin,CreateModelMixin实现群查和单增
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)
代码实现

urls.py

from django.conf.urls import url, include

from api import views

urlpatterns = [
url(r'^v4/books/$', views.BookListCreateView.as_view()),
url(from django.conf.urls import url, include

from api import views

urlpatterns = [
url(r'^v4/books/$', views.BookListCreateView.as_view()),
url(r'^v4/books/(?P.*)/$', views.BookListCreateView.as_view()),
]

工具视图的功能如果满足需求,只需要继承工具视图,添加 queryset,serializer_class

views.py

class BookListCreateView(ListCreateAPIView):
queryset = models.Book.objects.filter(is_delete=class BookListCreateView(ListCreateAPIView):
queryset = models.Book.objects.filter(is_delete=False)
serializer_class = serializers.BookModelSerializer

测试接口

群查
mark

单增
mark

入库成功
mark

其他方法

根据上述源码分析与示例和下面的图示,我们可以很容易知道这些类的功能和用法
mark

2.添加其他接口

代码实现
# 需要什么接口,直接继承就行
# 比如我们在群查,单增的基础上,添加单改接口
class BookListCreateView(ListCreateAPIView, UpdateAPIView):
queryset = models.Book.objects.filter(is_delete=# 需要什么接口,直接继承就行
# 比如我们在群查,单增的基础上,添加单改接口
class BookListCreateView(ListCreateAPIView, UpdateAPIView):
queryset = models.Book.objects.filter(is_delete=False)
serializer_class = serializers.BookModelSerializer
接口测试

mark

修改成功mark

3.后续问题

但是上述虽然可以实现简单接口,但是有时候需求会很复杂。

比如,来自前端用户的数据格式并不是和我们规定的一样,有可能传来空值,错误字符等等。这就需要对 request.data 进行过滤,尤其是在入库的时候。

另外,如果数据有误,DRF 并不知道你的字段是哪出的问题,所以抛异常是只会是数据错误。我们需要对每个字段的每种错误类型给出对应的返回值。

可以在 UpdateModelMixin 源码中看到,request.data 并没有进行过滤。

class UpdateModelMixin:

def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
# request.data 并没有进行过滤
serializer = self.get_serializer(instance, data=request.data, partial=partial)
class UpdateModelMixin:

def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
# request.data 并没有进行过滤
serializer = self.get_serializer(instance, data=request.data, partial=partial)
# ...

二、Viewsets:视图集

1.简单使用

DRF 提供了 Viewsets.py 视图集,再次封装之前的操作。最主要的是,可以通过设置 请求-函数 映射关系,来将请求方式与原有方法或自定义方法对应执行。

查看源码

mark

发现没有提供实际的方法

class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
"""
The GenericViewSet class does not provide any actions by default,
but does include the base set of generic view behavior, such as
the `get_object` and `get_queryset` methods.
"""
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
"""
The GenericViewSet class does not provide any actions by default,
but does include the base set of generic view behavior, such as
the `get_object` and `get_queryset` methods.
"""
pass

但是看到它继承了 ViewSetMixin,GenericAPIView

查看 ViewSetMixin 类的 as_view 方法

GenericViewSet 和 ViewSet 都继承了 ViewSetMixin,as_view 可以配置 请求-函数 映射

比如view = MyViewSet.as_view({'get': 'list', 'post': 'create'})

class ViewSetMixin:

@classonlymethod
def as_view(cls, actions=None, **initkwargs):
#...

def view(request, *args, **kwargs):
# 这里 cls 去解析前边例子中的 {'get': 'list', 'post': 'create'}
self = cls(**initkwargs)
self.action_map = actions

# methods拿到请求方法,比如get
for method, action in actions.items():
handler = getattr(self, action)
# 映射method get到执行函数handler list
setattr(self, method, handler)

if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get

self.request = request
self.args = args
self.kwargs = kwargs

# 继承 APIView 中的 dispatch 进行分发
return self.dispatch(request, *args, **kwargs)

class ViewSetMixin:

@classonlymethod
def as_view(cls, actions=None, **initkwargs):
#...

def view(request, *args, **kwargs):
# 这里 cls 去解析前边例子中的 {'get': 'list', 'post': 'create'}
self = cls(**initkwargs)
self.action_map = actions

# methods拿到请求方法,比如get
for method, action in actions.items():
handler = getattr(self, action)
# 映射method get到执行函数handler list
setattr(self, method, handler)

if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get

self.request = request
self.args = args
self.kwargs = kwargs

# 继承 APIView 中的 dispatch 进行分发
return self.dispatch(request, *args, **kwargs)

# ...
代码实现

这样的好处是,各种需求的接口的请求方式都可以用不同函数定义返回值。比如十大接口对应十个函数,分别码代码。

views.py

class BookGenericViewSet(RetrieveModelMixin, ListModelMixin, GenericViewSet):
queryset = models.Book.objects.filter(is_delete=False)
serializer_class = serializers.BookModelSerializer

def get_list(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)

def get_obj(self, request, *args, **kwargs):
class BookGenericViewSet(RetrieveModelMixin, ListModelMixin, GenericViewSet):
queryset = models.Book.objects.filter(is_delete=False)
serializer_class = serializers.BookModelSerializer

def get_list(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)

def get_obj(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)

urls.py

from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include
from django.views.static import serve
from django.conf import settings

from api import views

urlpatterns = [
# ...
url(r'^v5/books/$', views.BookGenericViewSet.as_view({'get':'get_list'})),
url(r'^v5/books/(?P<pk>.*)/$', views.BookGenericViewSet.as_view({'get':from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include
from django.views.static import serve
from django.conf import settings

from api import views

urlpatterns = [
# ...
url(r'^v5/books/$', views.BookGenericViewSet.as_view({'get':'get_list'})),
url(r'^v5/books/(?P<pk>.*)/$', views.BookGenericViewSet.as_view({'get':'get_obj'})),
]

2.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.
"""
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
代码实现

views.py

# 继承 ModelViewset 会直接拥有六大接口:单查,群查,单增,单删,单整体改,单局部改
# 需要注意:Destroy 需要重写
class BookModelViewSet(ModelViewSet):
queryset = models.Book.objects.filter(is_delete=False)
serializer_class = serializers.BookModelSerializer

# 删除操作
def destroy(self, request, *args, **kwargs):
instance = self.get_object() # type: models.Book
if not instance:
return APIResponse(1, "Delete fail")
instance.is_delete = True
instance.save()
return APIResponse(1, # 继承 ModelViewset 会直接拥有六大接口:单查,群查,单增,单删,单整体改,单局部改
# 需要注意:Destroy 需要重写
class BookModelViewSet(ModelViewSet):
queryset = models.Book.objects.filter(is_delete=False)
serializer_class = serializers.BookModelSerializer

# 删除操作
def destroy(self, request, *args, **kwargs):
instance = self.get_object() # type: models.Book
if not instance:
return APIResponse(1, "Delete fail")
instance.is_delete = True
instance.save()
return APIResponse(1, "Delete successful")

urls.py

from django.conf.urls import url

from api import views

urlpatterns = [
url(r'^v6/books/$', views.BookModelViewSet.as_view({'get':'list', 'post':'create'})),
url(r'^v6/books/(?P<pk>.*)/$', views.BookModelViewSet.as_view({'get': 'retrieve', 'post':'create', 'put': 'update', 'patch': 'partial_update', 'delete': from django.conf.urls import url

from api import views

urlpatterns = [
url(r'^v6/books/$', views.BookModelViewSet.as_view({'get':'list', 'post':'create'})),
url(r'^v6/books/(?P<pk>.*)/$', views.BookModelViewSet.as_view({'get': 'retrieve', 'post':'create', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'})),
]

接口测试
群查

mark

单查

mark

单删

mark
查看数据库
mark

单增

mark

入库成功mark

等等

总结

GenericAPIView 与 APIView 的区别与适用场景

1. GenericAPIView 视图类

GenericView 继承 GenericAPIView 视图类

适用于标准的接口请求,或实现标准的 Model 类操作接口。

案例: 用户查询时,发送 GET 请求,返回数据。

2. APIView 视图类

ViewSet 继承 APIView 视图类

实现不需要 Model 类操作,或非标准的 Model 类操作接口。比如,POST请求在标准的 Model 类操作用于新增接口,但以下案例并不符合这个标准。

案例 1: 请求手机验证码时,发送 POST 请求,不需要 Model 类的参与。

案例 2: 用户登录时,发送的 POST 请求,并不是完成数据的新增。POST 只是用于提交数据,返回值也不是登录用户信息,而是登录的认证信息。