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

python 复制代码
# 定义函数 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文件中。

python 复制代码
# 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())

使用示例

python 复制代码
# 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做的工作主要是基础框架上的优化,所有的业务逻辑都需要自行编写。

python 复制代码
class APIView(View):
    # 全局变量主要是一些认证、授权、限流等基础配置
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    permission_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)

使用示例:

python 复制代码
# hello_view.py
from rest_framework.views import APIView 
from rest_framework.response import Response

class 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做出的改变还是基础功能层面的,所有的业务逻辑仍然需要自行编写。

python 复制代码
# 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/

使用示例:

python 复制代码
# hello_viewset.py
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response

class HelloViewSet(ViewSet):

    def list(self, request):
        return Response("Hello viewset, get!"))

# url.py
from hello_viewset import HelloViewSet

system_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定义了对模型操作的基础方法,比如查询结果集、对结果集进行分页、过滤等。这些方法就像一个个零件工厂,比如玻璃厂、轮胎厂、螺丝厂,但它并不提供组装,只有组装厂才决定了把这些零件造成是汽车还是自行车还是坦克。

python 复制代码
# 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(零件工厂)为基础进行使用。

python 复制代码
# 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'] = True
        return 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作为标识的。

python 复制代码
# 支持原始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.*里的内容白白加重了我的选择困难症。

使用示例:

python 复制代码
################################
## 使用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 HelloListAPIView

system_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 HelloModelViewSet

system_url = routers.SimpleRouter()
system_url.register(r'hello', HelloModelViewSet, basename='hello')

urlpatterns += system_url.urls
相关推荐
数据智能老司机6 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机7 小时前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机7 小时前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机7 小时前
精通 Python 设计模式——性能模式
python·设计模式·架构
c8i7 小时前
drf初步梳理
python·django
每日AI新事件7 小时前
python的异步函数
python
这里有鱼汤8 小时前
miniQMT下载历史行情数据太慢怎么办?一招提速10倍!
前端·python
databook17 小时前
Manim实现脉冲闪烁特效
后端·python·动效
程序设计实验室18 小时前
2025年了,在 Django 之外,Python Web 框架还能怎么选?
python
倔强青铜三19 小时前
苦练Python第46天:文件写入与上下文管理器
人工智能·python·面试