django之视图
django vs drf
首先要区分django 和 django rest framework(drf)。
django是前后端不分离的,后端写模板(template)渲染成html之后返回给浏览器。
drf是适应前后端分离的架构,基于django做的封装,提供的rest API规范的框架。
什么是rest API。
它是一种开发api的风格范式。就像变量命名,你可以采用驼峰也可以下划线。
比如我要开发一套接口,实现对用户的新增、修改、删除,若果不适用rest风格,设计出的api可能是这样的。
uri | 作用 |
---|---|
/ListUser | 获取用户列表 |
/GetUser/?id={id} | 获取单个用户 |
/AddUser | 新增用户 |
/ModifyUser | 修改用户信息 |
/DeleteUser | 删除用户信息 |
可以看出对于一个对象(用户)的多个操作(增删改)都是靠定义不同的URI来区分的。每个URI使用什么http method没有严格要求。
rest api则把对象和动作两个概念做了分离。动作使用http method标识,对象则是纯名词。
http method | uri | 作用 |
---|---|---|
GET | /User | 获取用户列表 |
GET | /User/{id} | 获取单个用户 |
POST | /User | 新增用户 |
PUT | /User | 修改用户信息 |
DELETE | /User | 删除用户信息 |
可以看到restful风格的API更加简洁清晰。
django的设计模式(MTV)
随着软件设计的发展,出现了分层的设计模式。比如web站点开发中常见的MVC(model、view、controller)模式,model负责持久化数据,view负责生成用户界面,controller负责处理业务逻辑。
django就是遵照MVC模式设计的,只不过名称做了修改,可以叫做MTV(model、template、view)。其中django中的template对应mvc中的view,django中的view对应mvc中的controller。
django的视图层
Django的视图层是处理业务逻辑的核心,它负责处理用户的请求并返回响应数据。
按照官方文档:https://docs.djangoproject.com/zh-hans/5.1/
视图层包含了url部分和视图部分(view)。
url部分:
url部分比较简单,就是维护了一个大的路由表,每个路由项包含了两部分,一个uri路径,一个对应的处理函数。另外就是一些基本的处理函数。
视图部分(两种编写视图的方式):
1、基于函数的视图(Function Base View, FBV)。
# 定义函数 hello_view.py
def hello(request, *argv, **kwarg):return HttpResponse("Hello, World!")# 把函数注册到路由表 url.py
from hello_view import hello
urlpatterns = [path('hello/', hello),
]
FBV的优点就是直观,直接url对应到了处理函数。缺点就是可复用性差。
2、基于类的视图(Class Based View, CBV)。
基于类的视图是我们要讲解的重点,其实并不复杂,只是因为众多的继承和混入,导致看不到你继承的view的全貌。
django中视图的基类为class view(),代码位于django\views\generic\base.py文件中。
# view视图很简单,只有一个变量和两个函数需要关注
class View:# 定义了所有支持的http方法http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']# 将类转化为函数,因为url那部分只支持函数def as_view(cls, **initkwargs):return self.dispatch(request, *args, **kwargs)# dispatch负责将请求根据http method转发到类对应的函数# 比如请求是get方法,则转发到def get()# 请求是post方法,则转发到def post()def dispatch(self, request, *args, **kwargs):return getattr(self, request.method.lower())
使用示例
# hello_view.py
class HelloView(View):def get(self, request, *args, **kwargs):return HttpResponse("Hello, get!")def post(self, request, *args, **kwargs):return HttpResponse("Hello, post!")# url.py
from hello_view import HelloView
urlpatterns = [path('hello/', HelloView.as_view()),
]
drf view
rest framework中的view/viewset因为层层继承和代码混入(mixin)的使用,看起来特别乱,尤其不知道什么场景下选择哪种view作为基类合适,下面简单说下我个人理解。
选择使用哪个作为基类的时候,只考虑以下三个即可:
APIView:当处理的对象中没有模型(model)操作、且逻辑相对简单时选择,如健康检测接口。只支持http方法定义的函数入口。
ViewSet:当处理的对象中没有模型(model)操作,但同一个类中有多个处理方法,可以通过@action添加自定义端点。
ModelViewSet:当处理对象中有模型(model)操作的CURD操作。
还有几个也可以用,但不推荐。比如GenericAPIView、GenericViewSet,我认为是一些在代码抽象泛化过程中的中间态类。虽然也可以用,但不用这些也可以过的更好。
APIView
APIView和django中View的主要区别就是对request、response进行了封装,还可以通过配置进行可插拔式的认证、权限校验等。
可以看出APIView做的工作主要是基础框架上的优化,所有的业务逻辑都需要自行编写。
class APIView(View):# 全局变量主要是一些认证、授权、限流等基础配置authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSESpermission_classes = api_settings.DEFAULT_PERMISSION_CLASSES# as_view函数的主要还是父类View的逻辑,即调用dispatch转发到对应方法函数def as_view(cls, **initkwargs):view = super().as_view(**initkwargs) # 主流程def dispatch(self, request, *args, **kwargs): # 对request进行了封装,可以使用request.data获取用户通过POST, PUT和PATCH方法发过来的数据request = self.initialize_request(request, *args, **kwargs)try:# 认证、权限校验self.initial(request, *args, **kwargs)# 拿到对应的http method对应的处理函数# 比如请求是get方法,则转发到def get()# 请求是post方法,则转发到def post()handler = getattr(self, request.method.lower(),self.http_method_not_allowed)# 执行方法函数response = handler(request, *args, **kwargs)# 封装响应结果self.response = self.finalize_response(request, response, *args, **kwargs)def initial(self, request, *args, **kwargs):# 认证检查self.perform_authentication(request)# 权限检查self.check_permissions(request)# 限流检查self.check_throttles(request)
使用示例:
# hello_view.py
from rest_framework.views import APIView
from rest_framework.response import Responseclass HelloApiView(APIView):# 自己实现get方法的处理逻辑def get(self, request, *args, **kwargs):return Response("Hello api, get!")# 自己实现post方法的处理逻辑def post(self, request, *args, **kwargs):return Response("Hello api, post!")# url.py
from hello_view import HelloApiView
urlpatterns = [path('hello_api/', HelloApiView.as_view()),
]
ViewSet
viewSet是继承了views.APIView和ViewSetMixin。
ViewSetMixin唯一的变化就是引入了action,因此viewSet做出的改变还是基础功能层面的,所有的业务逻辑仍然需要自行编写。
# rest_framework/viewsets.py
class ViewSetMixin:def as_view(cls, actions=None, **initkwargs):def get_extra_actions(cls):def reverse_action(self, url_name, *args, **kwargs):def get_extra_action_url_map(self):
ViewSetMixin主要就是重写了as_view函数,不再使用http方法作为处理函数的入口了,改成了action函数作为入口。其他的一些函数都是为了处理action引入做的相关处理。
ViewSet Action | HTTP 方法 | APIView 对应处理函数 | 默认 URL 模式 | 示例场景 |
---|---|---|---|---|
list | GET | get() | /资源路径/ | 获取所有文章 GET /posts/ |
create | POST | post() | /资源路径/ | 创建新文章 POST /posts/ |
retrieve | GET | get() | /资源路径/{pk}/ | 获取单篇文章 GET /posts/1/ |
update | PUT | put() | /资源路径/{pk}/ | 全量更新文章 PUT /posts/1/ |
partial_update | PATCH | patch() | /资源路径/{pk}/ | 部分更新文章 PATCH /posts/1/ |
destroy | DELETE | delete() | /资源路径/{pk}/ | 删除文章 DELETE /posts/1/ |
自定义 Action | ||||
@action(detail=False) | 自定义方法 | 任意方法 | /资源路径/自定义动作名/ | 批量操作 POST /posts/bulk_delete/ |
@action(detail=True) | 自定义方法 | 任意方法 | /资源路径/{pk}/自定义动作名/ | 点赞文章 POST /posts/1/like/ |
使用示例:
# hello_viewset.py
from rest_framework.viewsets import ViewSet
from rest_framework.response import Responseclass HelloViewSet(ViewSet):def list(self, request):return Response("Hello viewset, get!"))# url.py
from hello_viewset import HelloViewSetsystem_url = routers.SimpleRouter()
system_url.register(r'hello', HelloViewSet, basename='hello')urlpatterns += system_url.urls
ModelViewSet
modelViewSet默认提供了对模型的增删改查操作,极大的简化了常规模型操作的代码量。
# rest_framework/viewsets里面的ModelViewSet代码
class ModelViewSet(mixins.CreateModelMixin,mixins.RetrieveModelMixin,mixins.UpdateModelMixin,mixins.DestroyModelMixin,mixins.ListModelMixin,GenericViewSet):# 可以看出ModelViewSet下面其实没有代码,都是靠继承来的
# 以下是继承关系图
APIView
└── GenericAPIView└── GenericViewSet (ViewSetMixin + GenericAPIView)└── ModelViewSet# APIView:前面说过了,封装了请求和响应,增加了权限认证等基础模块。
# GenericAPIView:提供了模型的定义和模型的基本操作函数,如get_queryset()等。
# GenericViewSet:引入了action
# ModelViewSet:加入了mixins中的CreateModelMixin、UpdateModelMixin等,才把所有散件串联起来。
所以想要理解ModelViewSet,就得先把GenericAPIView、GenericViewSet还有关键的mixin类搞清楚。
GenericAPIView(零件工厂)
GenericAPIView定义了对模型操作的基础方法,比如查询结果集、对结果集进行分页、过滤等。这些方法就像一个个零件工厂,比如玻璃厂、轮胎厂、螺丝厂,但它并不提供组装,只有组装厂才决定了把这些零件造成是汽车还是自行车还是坦克。
# rest_framework/generics.py
class GenericAPIView(views.APIView):# 基础查询结果集queryset = None# 序列化类serializer_class = None# 过滤、排序的类filter_backends = api_settings.DEFAULT_FILTER_BACKENDS# 分页的类pagination_class = api_settings.DEFAULT_PAGINATION_CLASS# 获取全量结果集,就是上面定义的self.queryset,可重写此方法实现定制def get_queryset(self):# 获取单条记录def get_object(self):# 获取序列化后的数据def get_serializer(self, *args, **kwargs):# 获取序列化的类def get_serializer_class(self):# 过滤数据def filter_queryset(self, queryset):# 对数据进行分页def paginate_queryset(self, queryset):
mixins(组装工厂)
mixins里的类就提供了组装方法,有新增数据的CreateModelMixin,有修改数据的UpdateModelMixin。但组装厂没有零件是干不了任何事的,所以mixins里的代码必需得以GenericAPIView(零件工厂)为基础进行使用。
# rest_framework/mixins.py
# 新增数据的逻辑,可以看出创建数据和核心逻辑数据校验和保存数据都是在序列化类中定义的
class CreateModelMixin: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):serializer.save()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)class RetrieveModelMixin:def retrieve(self, request, *args, **kwargs):# 获取单行数据instance = self.get_object()# 序列化后返回serializer = self.get_serializer(instance)return Response(serializer.data)# 全行更新和部分字段更新的逻辑,核心逻辑还是在序列化类中执行
class UpdateModelMixin: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):serializer.save()# 只更新部分字段def partial_update(self, request, *args, **kwargs):kwargs['partial'] = Truereturn self.update(request, *args, **kwargs)class DestroyModelMixin: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()
零件 + 组装打包后的独立工厂
为了方便用户,是否可以提供开箱即用的工厂模式,不要用户用之前还得自己组装。
有的,主要有两类,
一类是支持原始http方法的,位于generics.py文件里,以APIView作为标识的。
一类是支持action方法的,位于viewsets.py文件里,以ModelViewSet作为标识的。
# 支持原始http方法的独立工厂有,可以看出APIView类的方法,是在内部做了http方法到action方法的转化。
class CreateAPIView(mixins.CreateModelMixin,GenericAPIView):def post(self, request, *args, **kwargs):return self.create(request, *args, **kwargs)class ListAPIView(mixins.ListModelMixin,GenericAPIView):def get(self, request, *args, **kwargs):return self.list(request, *args, **kwargs)class RetrieveAPIView(mixins.RetrieveModelMixin,GenericAPIView):def get(self, request, *args, **kwargs):return self.retrieve(request, *args, **kwargs)class DestroyAPIView(mixins.DestroyModelMixin,GenericAPIView):def delete(self, request, *args, **kwargs):return self.destroy(request, *args, **kwargs)
……# 支持action方法的独立工厂有
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,mixins.ListModelMixin,GenericViewSet):class ModelViewSet(mixins.CreateModelMixin,mixins.RetrieveModelMixin,mixins.UpdateModelMixin,mixins.DestroyModelMixin,mixins.ListModelMixin,GenericViSet):
虽然generics.py里的类也是可以开箱及用的,但是还是需要用户了解细节,才能更好的组装使用,比如CreateAPIView只提供新增,如果希望同时支持新增和列表查询,就需要再加一个继承类mixins.ListModelMixin。
另外APIView不支持action,也就不能通过自定义action实现灵活的路由定制。所以综合下来,直接使用ModelViewSet更新,generics.*里的内容白白加重了我的选择困难症。
使用示例:
################################
## 使用APIView类
################################# hello_listApiView.py
from rest_framework.generics import ListAPIView
from models import Area
from serializers import AreaSerializer# 只包含获取列表功能
class HelloListAPIView(ListAPIView):queryset = Area.objects.all()serializer_class = AreaSerializer# url.py
from hello_listApiView import HelloListAPIViewsystem_url = routers.SimpleRouter()urlpatterns = [path('hello/', HelloListAPIView.as_view()),
]urlpatterns += system_url.urls################################
## 使用ModelViewSet类
################################# hello_ModelViewSet.py
from rest_framework.viewsets import ModelViewSet
from models import Area
from serializers import AreaSerializer# 提供了所有对模型的增删该查功能,代码还是这么多,大大节省了代码量
class HelloModelViewSet(ModelViewSet):queryset = Area.objects.all()serializer_class = AreaSerializer# url.py
from hello_ModelViewSet import HelloModelViewSetsystem_url = routers.SimpleRouter()
system_url.register(r'hello', HelloModelViewSet, basename='hello')urlpatterns += system_url.urls