zoukankan      html  css  js  c++  java
  • 视图家族 & 路由组件

    视图家族 & 路由组件

    视图基类:APIView、GenericAPIView
    视图工具类:mixins包下的五个类(六个方法)
    工具视图类:generics包下的所有GenericAPIView的子类
    视图集:viewsets包下的类

    GenericAPIView基类(基本不会单独使用,了解即可,但是是高级视图类的依赖基础)
    1)GenericAPIView继承APIView,所有APIView子类写法在继承GenericAPIView时可以保持一致
    2)GenericAPIView给我们提供了三个属性 queryset、serializer_class、lookup_field
    3)GenericAPIView给我们提供了三个方法 get_queryset、get_serializer、get_obj
    

    mixins包存放了视图工具类(不能单独使用,必须配合GenericAPIView使用)

    ​```
    CreateModelMixin:单增工具类
    	create方法
    	
    ListModelMixin:群查工具类
    	list方法
    
    RetrieveModelMixin:单查工具类
    	retrieve方法
    
    UpdateModelMixin:单整体局部改工具类
    	update方法
    
    DestroyModelMixin:单删工具类
    	destory方法
    
    generics包下的所有GenericAPIView的子类(就是继承GenericAPIView和不同mixins下的工具类的组合)
    
    ​```python
    """ 
    1)定义的视图类,继承generics包下已有的特点的GenericAPIView子类,可以在只初始化queryset和serializer_class两个类属性后,就获得特定的功能
    
    2)定义的视图类,自己手动继承GenericAPIView基类,再任意组合mixins包下的一个或多个工具类,可以实现自定义的工具视图类,获得特定的功能或功能们
    
    注:
    i)在这些模式下,不能实现单查群查共存(可以加逻辑区分,也可以用视图集知识)
    ii)DestroyModelMixin工具类提供的destory方法默认是从数据库中删除数据,所以一般删除数据的需求需要自定义逻辑
    """
    

    urls.py

    from django.conf.urls import url
    from . import views
    
    urlpatterns = [
     # ...
     
     url(r'^v1/books/$', views.BookV1APIView.as_view()),
     url(r'^v1/books/(?P<pk>d+)/$', views.BookV1APIView.as_view()),
    
     url(r'^v2/books/$', views.BookV2APIView.as_view()),
     url(r'^v2/books/(?P<pk>d+)/$', views.BookV2APIView.as_view()),
    
     url(r'^v3/books/$', views.BookV3APIView.as_view()),
     url(r'^v3/books/(?P<pk>d+)/$', views.BookV3APIView.as_view()),
    ]
    

    views.py

    # ----------------------------- 过渡写法:了解 -----------------------------
    
    from rest_framework.generics import GenericAPIView
    class BookV1APIView(GenericAPIView):
     # 将数据和序列化提示为类属性,所有的请求方法都可以复用
     queryset = models.Book.objects.filter(is_delete=False).all()
     serializer_class = serializers.BookModelSerializer
     lookup_field = 'pk'  # 可以省略,默认是pk,与url有名分组对应的
    
     # 群查
     def get(self, request, *args, **kwargs):
         # queryset = models.Book.objects.filter(is_delete=False).all()  # => 方法+属性两行代码
         queryset = self.get_queryset()
         # serializer = serializers.BookModelSerializer(instance=queryset, many=True)  # => 方法+属性两行代码
         serializer = self.get_serializer(instance=queryset, many=True)
         return APIResponse(results=serializer.data)
    
     # 单查
     # def get(self, request, *args, **kwargs):
     #     obj = self.get_object()
     #     serializer = self.get_serializer(obj)
     #     return APIResponse(results=serializer.data)
    
     # 单增
     def post(self, request, *args, **kwargs):
         # serializer = serializers.BookModelSerializer(data=request.data)
         serializer = self.get_serializer(data=request.data)  # 同样的步骤多了,好处就来了
         serializer.is_valid(raise_exception=True)
         obj = serializer.save()
         return APIResponse(result=self.get_serializer(obj).data, http_status=201)
     
    # ----------------------------- 过渡写法:了解 -----------------------------
    
    from rest_framework.generics import GenericAPIView
    from rest_framework import mixins
    class BookV2APIView(GenericAPIView, mixins.RetrieveModelMixin, mixins.CreateModelMixin):
     queryset = models.Book.objects.filter(is_delete=False).all()
     serializer_class = serializers.BookModelSerializer
    
     # 单查
     def get(self, request, *args, **kwargs):
         # obj = self.get_object()
         # serializer = self.get_serializer(obj)
         # return APIResponse(results=serializer.data)
    
         # return self.retrieve(request, *args, **kwargs)
    
         response = self.retrieve(request, *args, **kwargs)
         return APIResponse(result=response.data)
    
     # 单增
     def post(self, request, *args, **kwargs):
         return self.create(request, *args, **kwargs)
    
    
    # ----------------------------- 开发写法:常用 -----------------------------
    
    from rest_framework.generics import RetrieveAPIView
    class BookV3APIView(RetrieveAPIView):
     queryset = models.Book.objects.filter(is_delete=False).all()
     serializer_class = serializers.BookModelSerializer
    
     # 单查
     pass
    


    视图集与路由组件

    准备工作

    models.py

    from django.db import models
    
    # 基类:是抽象的(不会完成数据库迁移),目的是提供共有字段的
    class BaseModel(models.Model):
     is_delete = models.BooleanField(default=False)
     updated_time = models.DateTimeField(auto_now_add=True)
    
     class Meta:
         abstract = True  # 必须完成该配置
    
    class Book(BaseModel):
     name = models.CharField(max_length=64)
     price = models.DecimalField(max_digits=5, decimal_places=2, null=True)
     image = models.ImageField(upload_to='img', default='img/default.png')
    
     publish = models.ForeignKey(to='Publish', related_name='books', db_constraint=False, on_delete=models.DO_NOTHING)
     authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)
    
     @property  # @property字段默认就是read_only,且不允许修改
     def publish_name(self):
         return self.publish.name
    
     @property  # 自定义序列化过程
     def author_list(self):
         temp_author_list = []
         for author in self.authors.all():
             author_dic = {
                 "name": author.name
             }
             try:
                 author_dic['phone'] = author.detail.phone
             except:
                 author_dic['phone'] = ''
             temp_author_list.append(author_dic)
         return temp_author_list
    
    
    
    class Publish(BaseModel):
     name = models.CharField(max_length=64)
    
    
    class Author(BaseModel):
     name = models.CharField(max_length=64)
    
    
    class AuthorDetail(BaseModel):
     phone = models.CharField(max_length=11)
     author = models.OneToOneField(to=Author, related_name='detail', db_constraint=False, on_delete=models.CASCADE)
    

    serializers.py

    from rest_framework import serializers
    from . import models
    
    # 只有在资源需要提供群改,才需要定义ListSerializer,重写update方法
    class BookListSerializer(serializers.ListSerializer):
     def update(self, queryset, validated_data_list):
         return [
             self.child.update(queryset[index], validated_data) for index, validated_data in enumerate(validated_data_list)
         ]
    
    class BookModelSerializer(serializers.ModelSerializer):
     class Meta:
         list_serializer_class = BookListSerializer
         model = models.Book
         fields = ['name', 'price', 'image', 'publish', 'authors', 'publish_name', 'author_list']
         extra_kwargs = {
             'publish': {
                 'write_only': True
             },
             'authors': {
                 'write_only': True
             }
         }
    

    基于 GenericAPIView 的十大接口

    views.py

    # 十大接口:
    # 1)单查、群查、单增、单整体改、单局部改都可以直接使用
    # 2)单删不能直接使用,因为默认提供的功能是删除数据库数据,不是我们自定义is_delete字段值修改
    # 3)除了群查以为的接口,都要自己来实现
    
    # 注:给序列化类context赋值{'request': request},序列化类就可以自动补全后台图片链接
    
    from rest_framework.generics import GenericAPIView
    from rest_framework import mixins
    from . import models, serializers
    from rest_framework.response import Response
    
    class BookV1APIView(GenericAPIView,
                     mixins.RetrieveModelMixin,
                     mixins.ListModelMixin,
                     mixins.CreateModelMixin,
                     mixins.UpdateModelMixin):
    
     queryset = models.Book.objects.filter(is_delete=False).all()
     serializer_class = serializers.BookModelSerializer
    
     def get(self, request, *args, **kwargs):
         if 'pk' in kwargs:
             return self.retrieve(request, *args, **kwargs)  # 单查
    		
         # queryset = models.Book.objects.filter(is_delete=False).all()
         # 注:给序列化类context赋值{'request': request},序列化类就可以自动补全后台图片链接
         # serializer = serializers.BookModelSerializer(queryset, many=True, context={'request': request})
         # return Response(serializer.data)
         return self.list(request, *args, **kwargs)  # 群查
    
     def post(self, request, *args, **kwargs):
         if not isinstance(request.data, list):
             return self.create(request, *args, **kwargs)
    
         serializer = self.get_serializer(data=request.data, many=True)
         serializer.is_valid(raise_exception=True)
         self.perform_create(serializer)
         headers = self.get_success_headers(serializer.data)
         return Response(serializer.data, status=201, headers=headers)
    
     def delete(self, request, *args, **kwargs):
         pk = kwargs.get('pk')
         if pk:
             pks = [pk]
         else:
             pks = request.data
         try:
             rows = models.Book.objects.filter(is_delete=False, pk__in=pks).update(is_delete=True)
         except:
             return Response(status=400)
         if rows:
             return Response(status=204)
         return Response(status=400)
    
     def put(self, request, *args, **kwargs):
         if 'pk' in kwargs:
             return self.update(request, *args, **kwargs)
    
         pks = []
         try:
             for dic in request.data:
                 pks.append(dic.pop('pk'))
             objs = models.Book.objects.filter(is_delete=False, pk__in=pks)
             assert len(objs) == len(request.data)
         except:
             return Response(status=400)
         serializer = serializers.BookModelSerializer(instance=objs, data=request.data, many=True)
         serializer.is_valid(raise_exception=True)
         objs = serializer.save()
         return Response(serializers.BookModelSerializer(objs, many=True).data)
    
     def patch(self, request, *args, **kwargs):
         if 'pk' in kwargs:
             return self.partial_update(request, *args, **kwargs)
    
         pks = []
         try:
             for dic in request.data:
                 pks.append(dic.pop('pk'))
             objs = models.Book.objects.filter(is_delete=False, pk__in=pks)
             assert len(objs) == len(request.data)
         except:
             return Response(status=400)
         serializer = serializers.BookModelSerializer(instance=objs, data=request.data, many=True, partial=True)
         serializer.is_valid(raise_exception=True)
         objs = serializer.save()
         return Response(serializers.BookModelSerializer(objs, many=True).data)
    

    总结:

    配置queryset、serializer_class、lookup_field

    要自己定义get、post等方法,内部调用retrieve、create方法


    基于 generics 包下工具视图类的六大基础接口

    views.py

    # 六大基础接口
    # 1)直接继承generics包下的工具视图类,可以完成六大基础接口
    # 2)单查群查不能共存
    # 3)单删一般会重写
    
    from . import models, serializers
    from rest_framework.response import Response
    from rest_framework import generics
    class BookV2APIView(generics.ListAPIView,
                     generics.RetrieveAPIView,
                     generics.CreateAPIView,
                     generics.UpdateAPIView,
                     generics.DestroyAPIView):
     queryset = models.Book.objects.filter(is_delete=False).all()
     serializer_class = serializers.BookModelSerializer
    
     def get(self, request, *args, **kwargs):
         if 'pk' in kwargs:
             return self.retrieve(request, *args, **kwargs)
         return self.list(request, *args, **kwargs)
    
     def delete(self, request, *args, **kwargs):
         pk = kwargs.get('pk')
         models.Book.objects.filter(is_delete=False, pk=pk).update(is_delete=True)
         return Response(status=204)
    

    总结:

    配置queryset、serializer_class、lookup_field

    重写get处理单查群查共存即可

    delete方法是否重写看需求



    视图集

    解析actions,修改 请求分发 - 响应函数 映射关系

    """ ViewSetMixin类存在理由推到
    1)工具视图类,可以完成应付六大基础接口,唯一缺点是单查与群查接口无法共存
    	(list, retrieve, create, update, destory)
    	(只配置queryset、serializer_class、lookup_field)
    2)不能共存的原因:RetrieveAPIView和ListAPIView都是get方法,不管带不带pk的get请求,只能映射给一个get方法
    3)能不能修改映射关系:
    	比如将/books/的get请求映射给list方法,
    	将/books/(pk)/的get请求映射给retrieve方法,
    	甚至可以随意自定义映射关系
    """
    
    """ 继承视图集的视图类的as_view都是走ViewSetMixin类的,核心源码分析
    @classonlymethod
    def as_view(cls, actions=None, **initkwargs):
     # ...
    
     # 没有actions,也就是调用as_view()没有传参,像as_view({'get': 'list'})
     if not actions:
         raise TypeError("The `actions` argument must be provided when "
                         "calling `.as_view()` on a ViewSet. For example "
                         "`.as_view({'get': 'list'})`")
    
         # ...
    
         # 请求来了走view函数
         def view(request, *args, **kwargs):
             # ...
             # 解析actions,修改 请求分发 - 响应函数 映射关系
             self.action_map = actions
             for method, action in actions.items():  # method:get | action:list
                 handler = getattr(self, action)  # 从我们视图类用action:list去反射,所以handler是list函数,不是get函数
                 setattr(self, method, handler)  # 将get请求对应list函数,所以在dispath分发请求时,会将get请求分发给list函数
    
                 # ...
                 # 通过视图类的dispatch完成最后的请求分发
                 return self.dispatch(request, *args, **kwargs)
    
             # ...
             # 保存actions映射关系,以便后期使用
             view.actions = actions
             return csrf_exempt(view)
    """
    
    

    核心

    url(r'^v3/books/$', views.BookV3APIView.as_view(
     {'get': 'list', 'post': 'create', 'delete': 'multiple_destroy'}
    	)),
    
    url(r'^v3/books/(?P<pk>d+)/$', views.BookV3APIView.as_view(
         {'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'}
     )),
    

    views.py

    from rest_framework.viewsets import ModelViewSet
    from rest_framework.response import Response
    class BookV3APIView(ModelViewSet):
     queryset = models.Book.objects.filter(is_delete=False).all()
     serializer_class = serializers.BookModelSerializer
    
     # 可以在urls.py中as_view({'get': 'my_list'})自定义请求映射
     def my_list(self, request, *args, **kwargs):
         return Response('ok')
    
     # 需要完成字段删除,不是重写delete方法,而是重写destroy方法
     def destroy(self, request, *args, **kwargs):
         pk = kwargs.get('pk')
         models.Book.objects.filter(is_delete=False, pk=pk).update(is_delete=True)
         return Response(status=204)
    
    
     # 群删接口
     def multiple_destroy(self, request, *args, **kwargs):
         try:
             models.Book.objects.filter(is_delete=False, pk__in=request.data).update(is_delete=True)
         except:
             return Response(status=400)
         return Response(status=204)
    

    总结:

    1. ViewSetMixin 类:重写 as_view 方法

      作用:as_view({"get": "list"}) 来自定义请求与响应的映射关系

    2. 两个视图集基类:

      ViewSet: 与 Model 关系不是特别紧密

      GenericViewSet : 与 Model 关系特别紧

    3. 两个GenericViewSet的子类:

      ModelViewSet:六大基础接口共存

      ReadOnlyModelViewSet:只读接口

      注:都只需要配置queryset、serializer_class、lookup_field;根据需求决定是否重写某些方法

    4. 自己用两个视图集基类与 mixins 包形成自定义组合



    路由组件:必须配合视图集使用

    urls.py

    from django.conf.urls import url, include
    from . import views
    # 路由组件,必须配合视图集使用
    from rest_framework.routers import SimpleRouter
    router = SimpleRouter()
    
    # 以后就写视图集的注册即可:BookV3APIView和BookV4APIView都是视图集
    router.register('v3/books', views.BookV3APIView, 'book')
    router.register('v4/books', views.BookV4APIView, 'book')
    
    urlpatterns = [
     url('', include(router.urls))
    ]
    

    views.py

    from rest_framework.viewsets import ReadOnlyModelViewSet
    class BookV4APIView(ReadOnlyModelViewSet):
     queryset = models.Book.objects.filter(is_delete=False).all()
     serializer_class = serializers.BookModelSerializer
    


    自定义路由组件(了解)

    router.py

    from rest_framework.routers import SimpleRouter as DrfSimpleRouter
    from rest_framework.routers import Route, DynamicRoute
    
    class SimpleRouter(DrfSimpleRouter):
     routes = [
         # List route.
         Route(
             url=r'^{prefix}{trailing_slash}$',
             mapping={
                 'get': 'list',
                 'post': 'create',  # 注:群增只能自己在视图类中重写create方法,完成区分
                 'delete': 'multiple_destroy',  # 新增:群删
                 'put': 'multiple_update',  # 新增:群整体改
                 'patch': 'multiple_partial_update'  # 新增:群局部改
             },
             name='{basename}-list',
             detail=False,
             initkwargs={'suffix': 'List'}
         ),
         # Dynamically generated list routes. Generated using
         # @action(detail=False) decorator on methods of the viewset.
         DynamicRoute(
             url=r'^{prefix}/{url_path}{trailing_slash}$',
             name='{basename}-{url_name}',
             detail=False,
             initkwargs={}
         ),
         # Detail route.
         Route(
             url=r'^{prefix}/{lookup}{trailing_slash}$',
             mapping={
                 'get': 'retrieve',
                 'put': 'update',
                 'patch': 'partial_update',
                 'delete': 'destroy'
             },
             name='{basename}-detail',
             detail=True,
             initkwargs={'suffix': 'Instance'}
         ),
         # Dynamically generated detail routes. Generated using
         # @action(detail=True) decorator on methods of the viewset.
         DynamicRoute(
             url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$',
             name='{basename}-{url_name}',
             detail=True,
             initkwargs={}
         ),
     ]
    

    urls.py

    from .router import SimpleRouter
    router = SimpleRouter()
    
    router.register('car', views.CarAPIView, 'car')
    
    urlpatterns = [
         url('', include(router.urls))
    ]
    


    上传图片接口

    urls.py

    from django.conf.urls import url, include
    from . import views
    # 路由组件,必须配合视图集使用
    from rest_framework.routers import SimpleRouter
    router = SimpleRouter()
    
    # /books/image/(pk) 提交 form-data:用image携带图片
    router.register('books/image', views.BookUpdateImageAPIView, 'book')
    
    urlpatterns = [
     url('', include(router.urls))
    ]
    

    serializers.py

    class BookUpdateImageModelSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.Book
         fields = ['image']
    

    views.py

    # 上次文件 - 修改头像 - 修改海报
    from rest_framework.viewsets import GenericViewSet
    from rest_framework import mixins
    class BookUpdateImageAPIView(GenericViewSet, mixins.UpdateModelMixin):
     queryset = models.Book.objects.filter(is_delete=False).all()
     serializer_class = serializers.BookUpdateImageModelSerializer
    

    总结:

    上传图片:前台提交form-data,类型选择文件类型,后台用request.data和request.FILES都可以访问



    权限

    models.py

    from django.db import models
    
    # RBAC - Role-Based Access Control
    # Django的 Auth组件 采用的认证规则就是RBAC
    
    from django.contrib.auth.models import AbstractUser
    class User(AbstractUser):
     mobile = models.CharField(max_length=11, unique=True)
    
     def __str__(self):
         return self.username
    
    
    class Book(models.Model):
     name = models.CharField(max_length=64)
    
     def __str__(self):
         return self.name
    
    
    class Car(models.Model):
     name = models.CharField(max_length=64)
    
     def __str__(self):
         return self.name
    

    settings.py

    # 自定义User表,要配置
    AUTH_USER_MODEL = 'api.User'
    

    admin.py

    from django.contrib import admin
    from . import models
    
    from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin
    
    # 自定义User表后,admin界面管理User类
    class UserAdmin(DjangoUserAdmin):
     # 添加用户课操作字段
     add_fieldsets = (
         (None, {
             'classes': ('wide',),
             'fields': ('username', 'password1', 'password2', 'is_staff', 'mobile', 'groups', 'user_permissions'),
         }),
     )
     # 展示用户呈现的字段
     list_display = ('username', 'mobile', 'is_staff', 'is_active', 'is_superuser')
    
    
    admin.site.register(models.User, UserAdmin)
    admin.site.register(models.Book)
    admin.site.register(models.Car)
    
    # 1)像专门做人员权限管理的系统(CRM系统)都是公司内部使用,所以数据量都在10w一下,一般效率要求也不是很高
    # 2)用户量极大的常规项目,会分两种用户:前台用户(三大认证) 和 后台用户(BRAC来管理)
    # 结论:没有特殊要求的Django项目可以直接采用Auth组件的权限六表,不需要自定义六个表,也不需要断开表关系,但可能需要自定义User表
    

    做项目是否要分表管理前后台用户

    """
    1)是否需要分表
    答案:不需要
    理由:前后台用户共存的项目,后台用户量都是很少;做人员管理的项目,基本上都是后台用户;前后台用户量都大的会分两个项目处理
    
    2)用户权限六表是否需要断关联
    答案:不需要
    理由:前台用户占主导的项目,几乎需求只会和User一个表有关;后台用户占主导的项目,用户量不会太大
    
    3)Django项目有没有必须自定义RBAC六表
    答案:不需要
    理由:auth组件功能十分强大且健全(验证密码,创建用户等各种功能);admin、xadmin、jwt、drf-jwt组件都是依赖auth组件的(自定义RBAC六表,插件都需要自定义,成本极高)
    """
    

    权限六表

    RBAC-Role-Based Access Control

    三表

    三大认证规则

    总结

    # 1)后台用户对各表操作,是后台项目完成的,我们可以直接借助admin后台项目(Django自带的)
    # 2)后期也可以用xadmin框架来做后台用户权限管理
    
    # 3)前台用户的权限管理如何处理
    #   定义了一堆数据接口的视图类,不同的登录用户是否能访问这些视图类,能就代表有权限,不能就代表无权限
    #   前台用户权限用drf框架的 三大认证
    
    # 注:前台用户权限会基于 jwt 认证
    
  • 相关阅读:
    CentOS 6.5 编译安装Apache2.4
    Linux 服务器安全优化
    yum安装Apache2.4
    HDU 3308 线段树求区间最长连续上升子序列长度
    HDU 3607 线段树+DP+离散化
    POJ 3667 线段树区间合并裸题
    HDU 5726 线段树+dp
    牛客网多校训练第二场D Kth Minimum Clique
    HDU 4325 离散化+树状数组 或者 不使用树状数组
    HDU 2167 状压dp方格取数
  • 原文地址:https://www.cnblogs.com/kai-/p/12348963.html
Copyright © 2011-2022 走看看