• Django


    一、视图三部曲

    https://www.cnblogs.com/wupeiqi/articles/7805382.html

    使用混合(mixins)

    之前得视图部分

    # urls.py
    
    from django.conf.urls import url
    from django.contrib import admin
    
    from app01 import views
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^publishes/$', views.PublishView.as_view(),name="publish"),
        url(r'^publishes/(?P<pk>d+)/$', views.PublishDetailView.as_view(),name="detail_publish"),
        url(r"^books/$", views.BookView.as_view(),name="books"),
        url(r'^books/(?P<pk>d+)/$',views.BookDetailView.as_view(),name="detail_book")
    
    ]
    
    ------------------------------------------------------
    
    # views.py
    
    from rest_framework.views import APIView, Response
    from app01.serializers import *
    
    
    class PublishView(APIView):
        def get(self, request):
            publish_list = Publish.objects.all()
            ret = PublishModelSerializers(publish_list, many=True)
            return Response(ret.data)
    
        def post(self, request):
            ps = PublishModelSerializers(data=request.data)
            if ps.is_valid():
                ps.save()
                return Response(ps.data)
            else:
                return Response(ps.errors)
    
    class PublishDetailView(APIView):
        def get(self,request,pk):
            publish = Publish.objects.filter(pk=pk).first()
            ps = PublishModelSerializers(publish)
            return Response(ps.data)
    
        def put(self,request,pk):
            publish = Publish.objects.filter(pk=pk).first()
            ps = PublishModelSerializers(publish,data=request.data)
            if ps.is_valid():
                ps.save()
                return Response(ps.data)
            else:
                return Response(ps.errors)
    
        def delete(self,request,pk):
            Publish.objects.filter(pk=pk).delete()
            return Response()
        
    class BookView(APIView):
        def get(self, request):
            book_list = Book.objects.all()
            ret = BookModelSerializers(book_list, many=True,context={"request":request})
            return Response(ret.data)
    
        def post(self, request):
            bms = BookModelSerializers(data=request.data, many=False,context={"request":request})
            if bms.is_valid():
                bms.save()
                return Response(bms.data)
            else:
                return Response(bms.errors)
    
    
    class BookDetailView(APIView):
        def get(self,request,pk):
            book = Book.objects.filter(pk=pk).first()
            # 序列化
            bms = BookModelSerializers(book,context={"request":request})
            return Response(bms.data)
    
        def put(self,request,pk):
            book = Book.objects.filter(pk=pk).first()
            bms = BookModelSerializers(book,data=request.data,context={"request":request})
            if bms.is_valid():
                bms.save()
                return Response(bms.data)
            else:
                return Response(bms.errors)
    
        def delete(self,reqeust,pk):
            Book.objects.filter(pk=pk).delete()
            return Response()
    
    ---------------------------------------------------
    # serializers.py
    
    # -*- coding:utf-8 -*-
    from .models import *
    from rest_framework import serializers
    
    
    class PublishModelSerializers(serializers.ModelSerializer):
        class Meta:
            model = Publish
            fields = "__all__"
    
    
    class BookModelSerializers(serializers.ModelSerializer):
        class Meta:
            model = Book
            fields = "__all__"
    from django.db import models
    
    # Create your models here.
    
    class Book(models.Model):
        title = models.CharField(max_length=32)
        price = models.IntegerField()
        pub_date = models.DateField()
        publish = models.ForeignKey("Publish")
        authors = models.ManyToManyField("Author")
    
        def __str__(self):
            return self.title
    
    class Publish(models.Model):
        name = models.CharField(max_length=32)
        email = models.EmailField()
    
        def __str__(self):
            return self.name
    
    
    class Author(models.Model):
        name = models.CharField(max_length=32)
        age = models.IntegerField()
    
        def __str__(self):
            return self.name
    models.py

    1. mixin类编写视图

    from rest_framework import mixins, generics

    mixins.ListModelMixin,   mixins.CreateModelMixin,

    mixins.RetrieveModelMixin,   mixins.UpdateModelMixin,   mixins.DestroyModelMixin,

    generics.GenericAPIView

       url(r'^authors/$',views.AuthorView.as_view(),name="author"),
       url(r'^authors/(?P<pk>d+)',views.AuthorDetailView.as_view(),name="detail_author"),
    
    -----------------------------------------
    
    #########################   mixin类编写视图  ##############################
    
    from rest_framework import mixins, generics
    
    
    class AuthorView(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerializers
    
        def get(self, request, *args, **kwargs):
            return self.list(request, *args, **kwargs)
    
        def post(self, request, *args, **kwargs):
            return self.create(request, *args, **kwargs)
    
    
    class AuthorDetailView(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin,
                           generics.GenericAPIView):
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerializers
    
        def get(self, request, *args, **kwargs):
            return self.retrieve(request, *args, **kwargs)
    
        def put(self, request, *args, **kwargs):
            return self.update(request, *args, **kwargs)
    
        def delete(self, request, *args, **kwargs):
            return self.destroy(request, *args, **kwargs)
    
    ------------------------------------------------
    
    class AuthorModelSerializers(serializers.ModelSerializer):
        class Meta:
            model = Author
            fields = "__all__"

    2. 使用通用的基于类的视图

    from rest_framework import generics

    generics.ListCreateAPIView

    generics.RetrieveUpdateDestroyAPIView

    通过使用mixin类,我们使用更少的代码重写了这些视图,但我们还可以再进一步。REST框架提供了一组已经混合好(mixed-in)的通用视图,我们可以使用它来简化我们的views.py模块。

    #########################   使用通用得基于类得视图  ##############################
    
    from rest_framework import generics
    
    
    class AuthorView(generics.ListCreateAPIView):
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerializers
    
    
    class AuthorDetailView(generics.RetrieveUpdateDestroyAPIView):
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerializers

    3. viewsets.ModelViewSet

    views.AuthorModelView.as_view({"get": "list", "post": "create"})

    views.AuthorModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})

    from rest_framework  import viewsets

    viewsets.ModelViewSet

        url(r'^authors/$', views.AuthorModelView.as_view({"get": "list", "post": "create"}), name="author"),
        url(r'^authors/(?P<pk>d+)',
            views.AuthorModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}), name="detail_author"),
    
    ---------------------------------------------------
    
    #########################   viewsets.ModelViewSet  ##############################
    
    from rest_framework import viewsets
    
    
    class AuthorModelView(viewsets.ModelViewSet):
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerializers

      # 可重写,覆盖!  
      # def list(self,request,*args,**kwargs):pass

    效果:

    get  post  get  put  delete  都可访问!

       

    http://www.cnblogs.com/yuanchenqi/articles/8719520.html
    视图三部曲
    5中方法: 查(全部) 查(单条) 增 删 改
    逻辑封装起来了
    
    -----------------------------------
    
    class AuthorModelView(viewsets.ModelViewSet):
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerializers
        
    (1):url(r'^authors/$', views.AuthorModelView.as_view({"get":"list","post":"create"}), name='author'),
    (2):url(r'^authors/$', ViewSetMixin.as_view({"get":"list","post":"create"}), name='author'),
    (3):url(r'^authors/$', ViewsetMixin.View, name='author'),
    
    一旦用户 get 方式 访问 authors:
    ViewsetMixin.View():
        for method, action in actions.items(): # {"get":"list","post":"create"}
            handler = getattr(self, action)    # self.list  self.create
            setattr(self, method, handler)     # self.get = self.list  self.post = self.create
        
            # getattr(self,"get")  # self.list
            # getattr(self,"post") # self.create
        
        return self.dispatch()
        
    APIView.dispatch():
        if request.method.lower() in self.http_method_names:
            handler = getattr(self,request.method.lower())
            response = handler(request,*args,**kwargs)  # self.list()
            
            return response
    
    (ViewSetMixin)
    小总结 - 笔记

    二、认证组件

     生成随机字符串

    import hashlib, time
    
    def get_random_str(user):
        """ 生成随机 字符串 """
        ctime = str(time.time())
    
        md5 = hashlib.md5(bytes(user,encoding='utf-8'))
        md5.update(bytes(ctime,encoding="utf-8"))
    
        return md5.hexdigest()

    update_or_create

    #  update_or_create
    Token.objects.update_or_create(user=user,defaults={"token":random_str})

    返回json

    # import json
    # from  django.shortcuts import HttpResponse
    # return HttpResponse(json.dumps(res,ensure_ascii=False))
    

    # from django.http import JsonResponse # return JsonResponse(res) return Response(res)

     登录,生成随机token

    url(r'^login/$', views.LoginView.as_view(), name="login")
        
    ---------------------------------------------
    
    import hashlib, time
    
    def get_random_str(user):
        """ 生成随机 字符串 """
        ctime = str(time.time())
    
        md5 = hashlib.md5(bytes(user,encoding='utf-8'))
        md5.update(bytes(ctime,encoding="utf-8"))
    
        return md5.hexdigest()
    
    
    class LoginView(APIView):
        def post(self,request):
            name = request.data.get("name")
            pwd = request.data.get("pwd")
    
            user = User.objects.filter(name=name,pwd=pwd).first()
            res = {"state_code":1000,"msg":None}
            if user:
                random_str = get_random_str(user.name)
                #  update_or_create
                Token.objects.update_or_create(user=user,defaults={"token":random_str})
                res["token"] = random_str
            else:
                res["state_code"] = 1001 # 错误状态码
                res["msg"] = "用户名或密码错误"
    
            # import json
            # from  django.shortcuts import HttpResponse
            # return HttpResponse(json.dumps(res,ensure_ascii=False))
    
            # from django.http import JsonResponse
            # return JsonResponse(res)
    
            return Response(res)
    
    --------------------------------------------------------
    
    # models.py 
    
    class User(models.Model):
        name = models.CharField(max_length=32)
        pwd = models.CharField(max_length=32)
    
    
    class Token(models.Model):
        user = models.OneToOneField("User")
        token = models.CharField(max_length=128)
    
        def __str__(self):
            return self.token

    效果图:

     

    登录验证 - 局部

    authentication_classes = [TokenAuth]

    from rest_framework import exceptions

    from rest_framework.authentication import BaseAuthentication

    def authenticate(self,request):

      ... ... 

      if not token_obj:

         raise exceptions.AuthenticationFailed("验证失败")

      return (token_obj.user.name, token_obj)

     

    from rest_framework import exceptions
    
    # class TokenAuth(object):
    #     def authenticate(self,request):
    #         token = request.GET.get("token")
    #         token_obj = Token.objects.filter(token=token).first()
    #         if not token_obj:
    #             raise exceptions.AuthenticationFailed("验证失败")
    #
    #         return (token_obj.user.name, token_obj)
    #
    #     def authenticate_header(self,request):
    #         pass
    
    
    from rest_framework.authentication import BaseAuthentication
    
    class TokenAuth(BaseAuthentication):
        def authenticate(self,request):
            token = request.GET.get("token")
            token_obj = Token.objects.filter(token=token).first()
            if not token_obj:
                raise exceptions.AuthenticationFailed("验证失败")
    
            return (token_obj.user.name, token_obj)
    
    
    class BookView(APIView):
        authentication_classes = [TokenAuth]
    
        def get(self, request):
            book_list = Book.objects.all()
            ret = BookModelSerializers(book_list, many=True, context={"request": request})
            return Response(ret.data)
    
        def post(self, request):
            bms = BookModelSerializers(data=request.data, many=False, context={"request": request})
            if bms.is_valid():
                bms.save()
                return Response(bms.data)
            else:
                return Response(bms.errors)

    登录验证 - 全局

    settings 配置

      'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.TokenAuth']

    # settings.py
    
    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.TokenAuth']
    }
    
    -------------------------------------------
    # app01.utils
    
    from .models import *
    from rest_framework import exceptions
    from rest_framework.authentication import BaseAuthentication
    
    class TokenAuth(BaseAuthentication):
        def authenticate(self,request):
            token = request.GET.get("token")
            token_obj = Token.objects.filter(token=token).first()
            if not token_obj:
                raise exceptions.AuthenticationFailed("验证失败")
    
            return (token_obj.user.name, token_obj)
    
    --------------------------------------------
    # views.py
    
    class BookView(APIView):
        def get(self, request):
            book_list = Book.objects.all()
            ret = BookModelSerializers(book_list, many=True, context={"request": request})
            return Response(ret.data)
    
        def post(self, request):
            bms = BookModelSerializers(data=request.data, many=False, context={"request": request})
            if bms.is_valid():
                bms.save()
                return Response(bms.data)
            else:
                return Response(bms.errors)

    -----------------------------------------
    class AuthorModelView(viewsets.ModelViewSet):
    authentication_classes = [] # 加上这个,前提是全局有认证;加上这个,就走自己得,不认证了,自己没有,才走全局配置得!

    queryset = Author.objects.all()
    serializer_class = AuthorModelSerializers

    效果图:

         

                             

    三、权限组件

    权限 - 局部

    permission_classes = [SVIPPermission]

    has_permission(self,request,view):pass   # 固定得写法,根据源码来写得!

    authentication_classes = []                       # 登录页面 不需要验证,在全局配置得前提下

    class AuthorModelView(viewsets.ModelViewSet):
        authentication_classes = [TokenAuth]
        permission_classes = [SVIPPermission]
    
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerializers
    
    ----------------------------------------------
    # app01.utils.SVIPPermission
    class SVIPPermission(object):
        message = "只有超级用户才能访问"
        def has_permission(self,request,view):
            username = request.user
            user_type = User.objects.filter(name=username).first().user_type
            if user_type == 3:
                return True
            else:
                return False
    
    ----------------------------------------------
    
    class LoginView(APIView):
        authentication_classes = []
        ...
        
        ...

    权限 - 全局

    settings配置:

      'DEFAULT_PERMISSION_CLASSES': ['app01.utils.SVIPPermission']

    # settings.py

    REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.TokenAuth'], 'DEFAULT_PERMISSION_CLASSES': ['app01.utils.SVIPPermission'] }

    效果图:

       

    四、频率组件

     局部视图throttle

    class BookView(APIView):
        # authentication_classes = [TokenAuth]
        # permission_classes = [SVIPPermission]
        throttle_classes = [VisitRateThrottle]
        
        。。。  。。。 
        
    -----------------------------------------------
    
    from rest_framework.throttling import BaseThrottle
    
    VISIT_RECORD={}
    class VisitThrottle(BaseThrottle):
    
        def __init__(self):
            self.history=None
    
        def allow_request(self,request,view):
            remote_addr = request.META.get('REMOTE_ADDR')
            print(remote_addr)
            import time
            ctime=time.time()
    
            if remote_addr not in VISIT_RECORD:
                VISIT_RECORD[remote_addr]=[ctime,]
                return True
    
            history=VISIT_RECORD.get(remote_addr)
            self.history=history
    
            while history and history[-1]<ctime-60:
                history.pop()
    
            if len(history)<3:
                history.insert(0,ctime)
                return True
            else:
                return False
    
        def wait(self):
            import time
            ctime=time.time()
            return 60-(ctime-self.history[-1])       

    全局视图throttle

    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.TokenAuth'],
        'DEFAULT_PERMISSION_CLASSES': ['app01.utils.SVIPPermission'],
        'DEFAULT_THROTTLE_CLASSES': ['app01.utils.VisitThrottle'],
    }

    内置throttle类

    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.TokenAuth'],
        'DEFAULT_PERMISSION_CLASSES': ['app01.utils.SVIPPermission'],
        'DEFAULT_THROTTLE_CLASSES': ['app01.utils.VisitThrottle'],
        "DEFAULT_THROTTLE_RATES": {
            "visit_rate": "1/m",
        }
    }
    
    ------------------------------------
    
    from rest_framework.throttling import SimpleRateThrottle
    class VisitThrottle(SimpleRateThrottle):
    
        scope="visit_rate"
        def get_cache_key(self, request, view):
    
            return self.get_ident(request)

    http://www.cnblogs.com/yuanchenqi/articles/8719520.html

    五、解析器

    from rest_framework.parsers import JSONParser,FormParser,MultiPartParser,FileUploadParser
    """
    默认得是 JSONParser FormParser MultiPartParser 
    """
    
    class BookView(APIView):
    
        parser_classes = [JSONParser,FormParser]
        
        ... 

    request类

      django的request类和rest-framework的request类的源码解析

    局部视图

    from rest_framework.parsers import JSONParser,FormParser
    class PublishViewSet(generics.ListCreateAPIView):
        parser_classes = [FormParser,JSONParser]
        queryset = Publish.objects.all()
        serializer_class = PublshSerializers
        def post(self, request, *args, **kwargs):
            print("request.data",request.data)
            return self.create(request, *args, **kwargs)

    全局视图

    REST_FRAMEWORK={
        "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
        "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",],
        "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",],
        "DEFAULT_THROTTLE_RATES":{
            "visit_rate":"5/m",
        },
        "DEFAULT_PARSER_CLASSES":['rest_framework.parsers.FormParser',]
    }

    六、url路由控制

    url(r'',include(routers.urls)),

    from rest_framework import routers

    routers = routers.DefaultRouter()

    routers.register("authors",views.AuthorModelView)

    # urls.py
    
    from django.conf.urls import url,include
    from django.contrib import admin
    
    from app01 import views
    
    from rest_framework import routers
    routers = routers.DefaultRouter()
    routers.register("authors",views.AuthorModelView)
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^publishes/$', views.PublishView.as_view(), name="publish"),
        url(r'^publishes/(?P<pk>d+)/$', views.PublishDetailView.as_view(), name="detail_publish"),
        url(r"^books/$", views.BookView.as_view(), name="books"),
        url(r'^books/(?P<pk>d+)/$', views.BookDetailView.as_view(), name="detail_book"),
    
        # url(r'^authors/$',views.AuthorView.as_view(),name="author"),
        # url(r'^authors/(?P<pk>d+)',views.AuthorDetailView.as_view(),name="detail_author"),
    
        # url(r'^authors/$', views.AuthorModelView.as_view({"get": "list", "post": "create"}), name="author"),
        # url(r'^authors/(?P<pk>d+)',
        #     views.AuthorModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}), name="detail_author"),
    
    
        url(r'',include(routers.urls)),
    
        url(r'^login/$', views.LoginView.as_view(), name="login")
        
    ]
    
    ------------------------------------------
    
    # views.py
    
    from rest_framework import viewsets
    
    class AuthorModelView(viewsets.ModelViewSet):
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerializers

    效果图

      

     

    七、分页

    pnp = MyPageNumberPagination()

    books_page = pnp.paginate_queryset(book_list,request,self)

    
    
    from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination
    
    class MyPageNumberPagination(PageNumberPagination):
        page_size = 2
        page_query_param = "page"
        page_size_query_param = "size"
        max_page_size = 2   # 限制 size 得大小 不能超过多少!!
    
    class MyLimitOffsetPagination(LimitOffsetPagination):
        default_limit = 1
    
    ---------------------------
    
    class BookView(APIView):def get(self, request):
    
            book_list = Book.objects.all()
    
            pnp = MyPageNumberPagination()
            # pnp = MyLimitOffsetPagination()
    
            books_page = pnp.paginate_queryset(book_list,request,self)
    
            # ret = BookModelSerializers(book_list, many=True, context={"request": request})
            ret = BookModelSerializers(books_page, many=True)
    
            # return Response(ret.data)
        
         return pnp.get_paginated_response(ret.data)
    from rest_framework import viewsets
    
    class AuthorModelView(viewsets.ModelViewSet):
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerializers
    
        pagination_class = MyPageNumberPagination

    效果图

     

    day99
    
    1 CBV
    
    2 APIView
     class BookView(APIView):pass
     url(r'^books/$', views.BookView.as_view(),name="books"),
     url(r'^books/$', View类下的view,name="books"),
     一旦访问books/:  view(request)======APIView类下的dispatch()====请求方式对应的示例方法()
    
    3 def dispatch():
          #一 初始化操作
          # (1) 构建新的request:
          self.request=self.initial_request()
          # self.request._request
          # self.request.GET
          # self.request.data
          # (2) 执行组件
          # 认证,权限,频率
          # 认证:request.user
          self.initial(request, *args, **kwargs)
                     ====   # 认证组件
                            self.perform_authentication(request)
                               ==== request.user
                                         =====
                                              for authenticator in self.authenticators:  # [TokenAuth(),]
                                                        try:
                                                            user_auth_tuple = authenticator.authenticate(self)
                                                        except exceptions.APIException:
                                                            self._not_authenticated()
                                                            raise
    
                                                        if user_auth_tuple is not None:
                                                            self._authenticator = authenticator
                                                            self.user, self.auth = user_auth_tuple
                                                            return
    
    
                            # 权限组件
                            self.check_permissions(request)
                               ===========
                                     for permission in self.get_permissions():
                                            if not permission.has_permission(request, self):
                                                self.permission_denied(
                                                    request, message=getattr(permission, 'message', None)
                                                )
    
    
                            # 频率组件
                            self.check_throttles(request)
    
                              =============
                                for throttle in self.get_throttles():  # [VisitRateThrottle(),]
                                        if not throttle.allow_request(request, self):
                                            self.throttled(request, throttle.wait()) # 受限制
    
    
           # 分发
                if request.method.lower() in self.http_method_names:
                    handler = getattr(self,request.method.lower(),
                                      self.http_method_not_allowed)
    
                response = handler(request, *args, **kwargs)
    
                return response
    
    
    4 序列化组件
    
        class PublishSerializers(serializers.Serializer):
                name = serializers.CharField()
                email = serializers.CharField()
    
        class PublishModelSerializers(serializers.ModelSerializer):
                class Meta:
                    model=Publish
                    fields="__all__"
    
        # queryset或者model对象-------------》json数据
        ps=PublishSerializers(queryset,many=True)
        ps.data # [{},{},{}]
    
        ps=PublishSerializers(model_obj,many=False)
        ps.data # {}
    
    
        # json数据-------》记录
        # 添加操作
        ps=PublishSerializers(data=request.data)
        if ps.is_valid():
           ps.save()  # create
    
        # 更新操作
    
        ps=PublishSerializers(model_obj,data=request.data)
        if ps.is_valid():
           ps.save()  # update
    
    
    5 视图组件
        # 版本1:
              # Book表
                class BookView(APIView):
    
                    def get(self,request):
                        book_list=Book.objects.all()
                        bs=BookModelSerializers(book_list,many=True,context={'request': request})
                        return Response(bs.data)
                    def post(self,request):
                        # post请求的数据
                        bs=BookModelSerializers(data=request.data)
                        if bs.is_valid():
                            print(bs.validated_data)
                            bs.save()# create方法
                            return Response(bs.data)
                        else:
                            return Response(bs.errors)
    
    
                class BookDetailView(APIView):
    
                    def get(self,request,id):
    
                        book=Book.objects.filter(pk=id).first()
                        bs=BookModelSerializers(book,context={'request': request})
                        return Response(bs.data)
    
                    def put(self,request,id):
                        book=Book.objects.filter(pk=id).first()
                        bs=BookModelSerializers(book,data=request.data)
                        if bs.is_valid():
                            bs.save()
                            return Response(bs.data)
                        else:
                            return Response(bs.errors)
    
                    def delete(self,request,id):
                        Book.objects.filter(pk=id).delete()
    
                        return Response()
    
    
        # 版本2:mixIn
    
            from rest_framework import mixins
            from rest_framework import generics
    
            class AuthorView(mixins.ListModelMixin,mixins.CreateModelMixin,generics.GenericAPIView):
                queryset=Author.objects.all()
                serializer_class =AuthorModelSerializers
    
                def get(self,request, *args, **kwargs):
                    return self.list(request, *args, **kwargs)
                def post(self,request, *args, **kwargs):
                    return self.create(request, *args, **kwargs)
    
    
            class AuthorDetailView(mixins.RetrieveModelMixin,mixins.DestroyModelMixin,mixins.UpdateModelMixin,generics.GenericAPIView):
                queryset = Author.objects.all()
                serializer_class = AuthorModelSerializers
    
                def get(self,request,*args, **kwargs):
                    return self.retrieve(request,*args, **kwargs)
    
                def delete(self,request,*args, **kwargs):
                    return self.destroy(request,*args, **kwargs)
    
                def put(self,request,*args, **kwargs):
                    return self.retrieve(request,*args, **kwargs)
    
    
        # 版本3:基于通用类
            from rest_framework import mixins
            from rest_framework import generics
    
    
            class AuthorView(generics.ListCreateAPIView):
                queryset=Author.objects.all()
                serializer_class =AuthorModelSerializers
    
            class AuthorDetailView(generics.RetrieveUpdateDestroyAPIView):
                queryset = Author.objects.all()
                serializer_class = AuthorModelSerializers
    
        # 版本4
         class AuthorModelView(viewsets.ModelViewSet):
                queryset = Author.objects.all()
                serializer_class = AuthorModelSerializers
    
         url(r'^authors/$', views.AuthorModelView.as_view({"get":"list","post":"create"}),name="author"),
         url(r'^authors/(?P<pk>d+)/$', views.AuthorModelView.as_view({"get":"retrieve","put"
    
         流程:
             url(r'^authors/$', views.AuthorModelView.as_view({"get":"list","post":"create"}),name="author"),
             url(r'^authors/$', ViewSetMixin.as_view({"get":"list","post":"create"}),name="author"),
             url(r'^authors/$', ViewSetMixin类下的view),
             一旦访问 /authors/:
                  ViewSetMixin
                 def  view():
                    for method, action in actions.items(): # {"get":"list","post":"create"}
                        handler = getattr(self, action)    # self.list  self.create
                        setattr(self, method, handler)
    
                    self.dispatch(request, *args, **kwargs)
    
                APIView类下的self.dispatch
                      # 分发
                        if request.method.lower() in self.http_method_names:
                            handler = getattr(self,request.method.lower(),
                                              self.http_method_not_allowed)
    
    
                        response = handler(request, *args, **kwargs) # self.list()
    
                        return response
    
    
    6 认证权限频率 组件
    
        request.META:
            {'ALLUSERSPROFILE': 'C:\ProgramData',
            'APPDATA': 'C:\Users\Administrator\AppData\Roaming',
            'COMMONPROGRAMFILES': 'C:\Program Files\Common Files',
            'COMMONPROGRAMFILES(X86)': 'C:\Program Files (x86)\Common Files',
            'COMMONPROGRAMW6432': 'C:\Program Files\Common Files',
            'COMPUTERNAME': 'PC201712041709',
            'COMSPEC': 'C:\Windows\system32\cmd.exe',
            'DJANGO_SETTINGS_MODULE': 'restdemo.settings',
            'FP_NO_HOST_CHECK': 'NO', 'HOMEDRIVE': 'C:',
            'HOMEPATH': '\Users\Administrator',
            'LOCALAPPDATA': 'C:\Users\Administrator\AppData\Local',
            'LOGONSERVER': '\\PC201712041709',
            'NUMBER_OF_PROCESSORS': '4', 'OS': 'Windows_NT',
            'PATH': 'C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Users\Administrator\AppData\Local\Programs\Python\Python36;C:\Users\Administrator\AppData\Local\Programs\Python\Python36\Scripts;C:\Python27;E:\MySQL Server 5.6\bin;C:\Users\Administrator\AppData\Local\Programs\Python\Python36\Scripts\;C:\Users\Administrator\AppData\Local\Programs\Python\Python36\;C:\Users\Administrator\AppData\Local\atom\bin',
            'PATHEXT': '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC',
            'PROCESSOR_ARCHITECTURE': 'AMD64',
            'PROCESSOR_IDENTIFIER': 'Intel64 Family 6 Model 60 Stepping 3, GenuineIntel',
            'PROCESSOR_LEVEL': '6', 'PROCESSOR_REVISION': '3c03',
            'PROGRAMDATA': 'C:\ProgramData',
            'PROGRAMFILES': 'C:\Program Files',
            'PROGRAMFILES(X86)': 'C:\Program Files (x86)',
            'PROGRAMW6432': 'C:\Program Files',
            'PSMODULEPATH': 'C:\Windows\system32\WindowsPowerShell\v1.0\Modules\',
            'PUBLIC': 'C:\Users\Public', 'PYCHARM_HOSTED': '1', 'PYTHONIOENCODING': 'UTF-8',
            'PYTHONPATH': 'C:\Users\Administrator\PycharmProjects\s9\restdemo', 'PYTHONUNBUFFERED': '1',
            'SESSIONNAME': 'Console', 'SYSTEMDRIVE': 'C:', 'SYSTEMROOT': 'C:\Windows',
            'TEMP': 'C:\Users\ADMINI~1\AppData\Local\Temp', 'TMP': 'C:\Users\ADMINI~1\AppData\Local\Temp',
            'USERDOMAIN': 'PC201712041709',
            'USERNAME': 'Administrator',
            'USERPROFILE': 'C:\Users\Administrator',
            'WINDIR': 'C:\Windows', 'WINDOWS_TRACING_FLAGS': '3',
            'WINDOWS_TRACING_LOGFILE': 'C:\BVTBin\Tests\installpackage\csilogfile.log',
            'RUN_MAIN': 'true', 'SERVER_NAME': 'PC201712041709',
            'GATEWAY_INTERFACE': 'CGI/1.1', 'SERVER_PORT': '8000',
            'REMOTE_HOST': '',
            'CONTENT_LENGTH': '',
            'SCRIPT_NAME': '',
            'SERVER_PROTOCOL': 'HTTP/1.1',
            'SERVER_SOFTWARE': 'WSGIServer/0.2',
            'REQUEST_METHOD': 'GET',
            'PATH_INFO': '/authors/',
            'QUERY_STRING': 'token=8204b8e3ac40bf59ae480d17c146b51a',
            'REMOTE_ADDR': '127.0.0.1',
            'CONTENT_TYPE': 'text/plain',
            'HTTP_HOST': '127.0.0.1:8000',
            'HTTP_CONNECTION': 'keep-alive',
            'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
            'HTTP_UPGRADE_INSECURE_REQUESTS': '1',
            'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
            'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.9', 'HTTP_COOKIE': 'csrftoken=jtus3l4GJEc9TFXWYCWxkBIZprcOv7C1vFMIyOHs7Zkxt015FwVZ2KEEeDV6LOyN', 'wsgi.input': <_io.BufferedReader name=832>, 'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>, 'wsgi.version': (1, 0), 'wsgi.run_once': False, 'wsgi.url_scheme': 'http', 'wsgi.multithread': True, 'wsgi.multiprocess': False, 'wsgi.file_wrapper': <class 'wsgiref.util.FileWrapper'>}
    
    
    7 解析器-----数据解析器
        from rest_framework.parsers import JSONParser,FormParser,MultiPartParser,FileUploadParser
        parser_classes = [JSONParser,FormParser]
    
    8 路由控制
        针对:
             url(r'^authors/$', views.AuthorModelView.as_view({"get":"list","post":"create"}),name="author"),
             url(r'^authors/(?P<pk>d+)/$', views.AuthorModelView.as_view({"get":"retrieve","put":"update","delete":"destroy"}),name="detailauthor"),
    
    
             class AuthorModelView(viewsets.ModelViewSet):
    
                    queryset = Author.objects.all()
                    serializer_class = AuthorModelSerializers
    
    
    
             url(r'^books/$', views.BookModelView.as_view({"get":"list","post":"create"}),name="author"),
             url(r'^books/(?P<pk>d+)/$', views.BookModelView.as_view({"get":"retrieve","put":"update","delete":"destroy"}),name="detailbook"),
    
    
    
             class AuthorModelView(viewsets.ModelViewSet):
    
                    queryset = Author.objects.all()
                    serializer_class = AuthorModelSerializers
    
    
    9 分页
    
    
    
    10 响应器  Response
    总结 - 笔记
    REST_FRAMEWORK = {
        # 'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.TokenAuth'],
        # 'DEFAULT_PERMISSION_CLASSES': ['app01.utils.SVIPPermission'],
        # 'DEFAULT_THROTTLE_CLASSES': ['app01.utils.VisitThrottle'],
        # "DEFAULT_THROTTLE_RATES": {
        #     "visit_rate": "1/m",
        # }
        # "PAGE_SIZE":2
    }
    settings.py
    from django.conf.urls import url,include
    from django.contrib import admin
    
    from app01 import views
    
    from rest_framework import routers
    routers = routers.DefaultRouter()
    routers.register("authors",views.AuthorModelView)
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^publishes/$', views.PublishView.as_view(), name="publish"),
        url(r'^publishes/(?P<pk>d+)/$', views.PublishDetailView.as_view(), name="detail_publish"),
        url(r"^books/$", views.BookView.as_view(), name="books"),
        url(r'^books/(?P<pk>d+)/$', views.BookDetailView.as_view(), name="detail_book"),
    
        # url(r'^authors/$',views.AuthorView.as_view(),name="author"),
        # url(r'^authors/(?P<pk>d+)',views.AuthorDetailView.as_view(),name="detail_author"),
    
        # url(r'^authors/$', views.AuthorModelView.as_view({"get": "list", "post": "create"}), name="author"),
        # url(r'^authors/(?P<pk>d+)',
        #     views.AuthorModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}), name="detail_author"),
    
    
        url(r'',include(routers.urls)),
    
        url(r'^login/$', views.LoginView.as_view(), name="login")
        
    ]
    urls.py
    from .models import *
    from rest_framework import exceptions
    from rest_framework.authentication import BaseAuthentication
    
    class TokenAuth(BaseAuthentication):
        def authenticate(self,request):
            token = request.GET.get("token")
            token_obj = Token.objects.filter(token=token).first()
            if not token_obj:
                raise exceptions.AuthenticationFailed("验证失败")
    
            return (token_obj.user.name, token_obj)
    
    class SVIPPermission(object):
        message = "只有超级用户才能访问"
        def has_permission(self,request,view):
            username = request.user
            user_type = User.objects.filter(name=username).first().user_type
            if user_type == 3:
                return True
            else:
                return False
    
    
    # from rest_framework.throttling import BaseThrottle
    #
    # VISIT_RECORD={}
    # class VisitThrottle(BaseThrottle):
    #
    #     def __init__(self):
    #         self.history=None
    #
    #     def allow_request(self,request,view):
    #         remote_addr = request.META.get('REMOTE_ADDR')
    #         print(remote_addr)
    #         import time
    #         ctime=time.time()
    #
    #         if remote_addr not in VISIT_RECORD:
    #             VISIT_RECORD[remote_addr]=[ctime,]
    #             return True
    #
    #         history=VISIT_RECORD.get(remote_addr)
    #         self.history=history
    #
    #         while history and history[-1]<ctime-60:
    #             history.pop()
    #
    #         if len(history)<3:
    #             history.insert(0,ctime)
    #             return True
    #         else:
    #             return False
    #
    #     def wait(self):
    #         import time
    #         ctime=time.time()
    #         return 60-(ctime-self.history[-1])
    
    from rest_framework.throttling import SimpleRateThrottle
    class VisitThrottle(SimpleRateThrottle):
    
        scope="visit_rate"
        def get_cache_key(self, request, view):
    
            return self.get_ident(request)
    utils.py
    from django.shortcuts import render, HttpResponse
    
    
    from django.views import View
    from .models import *
    import json
    from rest_framework import serializers
    from rest_framework.views import APIView
    from rest_framework.response import Response
    
    
    # 为queryset,model 对象 做序列化得
    # class PublishSerializers(serializers.Serializer):
    #     name = serializers.CharField()
    #     email = serializers.CharField()
    
    
    class PublishModelSerializers(serializers.ModelSerializer):
        class Meta:
            model = Publish
            fields = "__all__"
    
    
    class BookModelSerializers(serializers.ModelSerializer):
        class Meta:
            model = Book
            fields = "__all__"
        
        # 显示超链接
        publish = serializers.HyperlinkedIdentityField(
            view_name='detailpublish', # 别名 含正则表达式
            lookup_field= 'publish_id',
            lookup_url_kwarg='pk'
    
        )
    
        # publish = serializers.CharField(source="publish.pk")
        # publish = serializers.CharField()
        # authors = serializers.CharField(source="authors.all")
        # authors = serializers.SerializerMethodField()
        # def get_authors(self,obj):
        #     temp = []
        #     for obj in obj.authors.all():
        #         temp.append(obj.name)
        #     return temp
    
        # def create(self, validated_data):
        #     print('--->',validated_data)
        #     book = Book.objects.create(title=validated_data["title"],price=validated_data['price'],
        #                         pub_date=validated_data['pub_date'],publish_id=validated_data['publish']['pk'])
        #     book.authors.add(*validated_data['authors'])
        #
        #     return book
    
    
    class AuthorModelSerializers(serializers.ModelSerializer):
        class Meta:
            model = Author
            fields = "__all__"
    serializer.py
    from django.db import models
    
    
    # Create your models here.
    
    class User(models.Model):
        name = models.CharField(max_length=32)
        pwd = models.CharField(max_length=32)
    
        type_choices = ((1,"普通用户"),(2,"VIP"),(3,"SVIP"))
        user_type = models.IntegerField(choices=type_choices,default=1)
    
    
    class Token(models.Model):
        user = models.OneToOneField("User")
        token = models.CharField(max_length=128)
    
        def __str__(self):
            return self.token
    
    
    class Book(models.Model):
        title = models.CharField(max_length=32)
        price = models.IntegerField()
        pub_date = models.DateField()
        publish = models.ForeignKey("Publish")
        authors = models.ManyToManyField("Author")
    
        def __str__(self):
            return self.title
    
    
    class Publish(models.Model):
        name = models.CharField(max_length=32)
        email = models.EmailField()
    
        def __str__(self):
            return self.name
    
    
    class Author(models.Model):
        name = models.CharField(max_length=32)
        age = models.IntegerField()
    
        def __str__(self):
            return self.name
    models.py
    from django.shortcuts import render,HttpResponse
    
    # Create your views here.
    
    from django.views import View
    from .models import *
    import json
    from rest_framework import serializers
    from rest_framework.views import APIView
    from rest_framework.response import Response
    
    from app01.serializer import *
    
    class PublishView(APIView):
        def get(self, request):
            publish_list = Publish.objects.all()
            ret = PublishModelSerializers(publish_list, many=True)
            return Response(ret.data)
    
        def post(self, request):
            ps = PublishModelSerializers(data=request.data)
            if ps.is_valid():
                ps.save()
                return Response(ps.data)
            else:
                return Response(ps.errors)
    
    
    class PublishDetailView(APIView):
        def get(self, request, pk):
            publish = Publish.objects.filter(pk=pk).first()
            ps = PublishModelSerializers(publish)
            return Response(ps.data)
    
        def put(self, request, pk):
            publish = Publish.objects.filter(pk=pk).first()
            ps = PublishModelSerializers(publish, data=request.data)
            if ps.is_valid():
                ps.save()
                return Response(ps.data)
            else:
                return Response(ps.errors)
    
        def delete(self, request, pk):
            Publish.objects.filter(pk=pk).delete()
            return Response()
    
    
    from rest_framework.parsers import JSONParser,FormParser,MultiPartParser,FileUploadParser
    """
    默认得是 JSONParser FormParser  MultiPartParser
    """
    # Book
    from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination
    class MyPageNumberPagination(PageNumberPagination):
        page_size = 1
        page_query_param = "page"
        page_size_query_param = "size"
        max_page_size = 2  # 限制 size 得大小 不能超过多少!!
    # # http://127.0.0.1:8000/books/?page=2&size=2
    
    class MyLimitOffsetPagination(LimitOffsetPagination):
        default_limit = 1
        # limit_query_param =
    # http://127.0.0.1:8000/books/?limit=2&offset=2
    
    # 偏移
    
    class BookView(APIView):
        # authentication_classes = [TokenAuth]
    
        parser_classes = [JSONParser,FormParser]
    
        def get(self,request):
    
            print("user:--->", request.user)
            print(request.auth)
    
            book_list = Book.objects.all()
    
            # 分页 page_size 配置 setting 全局得 单独怎么设置?  写个类
            # from rest_framework.pagination import PageNumberPagination
    
            # pnp = PageNumberPagination()
            # pnp = MyPageNumberPagination()
            pnp = MyLimitOffsetPagination()
    
            books_page = pnp.paginate_queryset(book_list,request,self)
    
            # bs = BookModelSerializers(book_list,many=True,context={'request': request})
            bs = BookModelSerializers(books_page,many=True,context={'request': request})
    
            # return HttpResponse(bs.data)
            return Response(bs.data)
    
        def post(self,request):
    
            print('data:--->', request.data,type(request.data))
    
            # post 请求的数据
            bs = BookModelSerializers(data=request.data,context={'request': request})
            if bs.is_valid():
                bs.save()  # create 方法
                print(bs.validated_data)
                return Response(bs.data)
            else:
                return Response(bs.errors)
    
    class BookDetailView(APIView):
        def get(self,request,id):
            book = Book.objects.filter(pk=id).first()
            # 序列化
            bs = BookModelSerializers(book)
            return Response(bs.data)
    
        def put(self,request,id):
    
    
    
            book = Book.objects.filter(pk=id).first()
            bs = BookModelSerializers(book,data=request.data)
            if bs.is_valid():
                bs.save()
                return Response(bs.data)
            else:
                return Response(bs.errors)
    
    
        def delete(self,request,id):
            Book.objects.filter(pk=id).delete()
            return Response()
    
    
    
    # Author
    # 逻辑复用
    # 三种方法
    
    #########################   mixin类编写视图  ##############################
    
    # from rest_framework import mixins, generics
    #
    # class AuthorView(mixins.ListModelMixin,mixins.CreateModelMixin,generics.GenericAPIView):
    #     queryset = Author.objects.all()
    #     serializer_class = AuthorModelSerializers
    #
    #     def get(self,request,*args,**kwargs):
    #         return self.list(request,*args,**kwargs)
    #
    #     def post(self,request,*args,**kwargs):
    #         return self.create(self, request, *args, **kwargs)
    #
    #
    # class AuthorDetailView(mixins.RetrieveModelMixin,mixins.DestroyModelMixin,mixins.UpdateModelMixin,generics.GenericAPIView):
    #     queryset = Author.objects.all()
    #     serializer_class = AuthorModelSerializers
    #
    #     def get(self,request,pk,*args,**kwargs):
    #         return self.retrieve(request,pk,*args,**kwargs)
    #
    #     def delete(self,request,*args,**kwargs):
    #         return self.destroy(request,*args,**kwargs)
    #
    #     def put(self,request,*args,**kwargs):
    #         return self.update(request,*args,**kwargs)
    
    
    #########################   使用得通用得基于类得视图  ##############################
    
    # from rest_framework import mixins, generics
    #
    # class AuthorView(generics.ListCreateAPIView):
    #     queryset = Author.objects.all()
    #     serializer_class = AuthorModelSerializers
    #
    #
    # class AuthorDetailView(generics.RetrieveUpdateDestroyAPIView):
    #     queryset = Author.objects.all()
    #     serializer_class = AuthorModelSerializers
    
    #########################   viewsets.ModelViewSet  ##############################
    # url 需要是一趟线 走一个视图类
    # url 中 利用参数 来指定 什么方式 用什么方法 执行
    from .utils import TokenAuth,SVIPPermission
    from rest_framework import viewsets
    
    
    class VisitRateThrottle(object):
        def allow_request(self,request,view):
            # 要求访问站点得频率不能超过每分钟20次
            if 1:
                # 每次来 存下来 比对一下,间隔多久 超过一分钟
                # IP, 请求首行(request.method request.path)
                #  请求头(request.meta) 请求体(request.body)
                print("meta:----->",request.META)
                print(request.META.get("REMOTE_ADDR"))  # 客户端得ip 这里面 你要保存什么  ip 时间 记录下来;
                                                          # 频率 限制 实现 功能!!!
    
                return True
            else:
                return False
    
    
    from rest_framework.response import Response
    
    
    class AuthorModelView(viewsets.ModelViewSet):
    
        # authentication_classes = [TokenAuth,]
        # permission_classes = [SVIPPermission,]  取配全局八
        # throttle_classes = [VisitRateThrottle]
    
    
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerializers
    
        pagination_class = MyPageNumberPagination
    
    # 完美!  ok
    
    # 类得继承 , 表示形式 需求 展示 数据 ,覆盖方法 单独写 类得继承 可重写 覆盖
    #
    #     def list(self,request,*args,**kwargs):
    #         pass
    
    
    # ------------------------------------------------------
    
    def get_random_str(user):
        import hashlib,time
        ctime=str(time.time())
    
        md5=hashlib.md5(bytes(user,encoding="utf8"))
        md5.update(bytes(ctime,encoding="utf8"))
    
        return md5.hexdigest()
    
    
    class LoginView(APIView):
        authentication_classes = []
        def post(self,request):
            name = request.data.get("name")
            pwd = request.data.get("pwd")
            user = User.objects.filter(name=name,pwd=pwd).first()
            res = {"state_code":1000,"msg":None}
            if user:
                random_str = get_random_str(user.name)
                token = Token.objects.update_or_create(user=user,defaults={"token":random_str})
                res['token'] = random_str
            else:
                res["status_code"] = 1001 # 错误状态码
                res['msg'] = "用户名或密码错误"
    
            import json
            return Response(json.dumps(res,ensure_ascii=False))
    views.py

    八、渲染器、版本

    配置:

    1.添加配置

    REST_FRAMEWORK = {
        'DEFAULT_RENDERER_CLASSES':['rest_framework.renderers.JSONRenderer','rest_framework.renderers.BrowsableAPIRenderer'],
        'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
        'ALLOWED_VERSIONS': ['v1', 'v2'],  # 允许的版本
        'VERSION_PARAM': 'version',  # 参数
        'DEFAULT_VERSION': 'v1',  # 默认版本
    }

    2.设置路由:

    luffycity/urls.py

    from django.conf.urls import url,include
    from django.contrib import admin
    
    urlpatterns = [
        # url(r'^admin/', admin.site.urls),
        url(r'^api/(?P<version>w+)/', include('api.urls')),
    ]

    api/urls.py

    from django.conf.urls import url
    from api.views import course
    
    urlpatterns = [
        url(r'^course/$', course.CourseView.as_view()),
    ]

    3.获取版本

    request.version 获取版本  
  • 相关阅读:
    python及pandas,numpy等知识点技巧点学习笔记
    人工智能,大数据,云计算大杂烩
    python开发环境
    机器学习vs深度学习及其知识点
    深入理解SVG坐标体系和transformations- viewport, viewBox,preserveAspectRatio
    军队改革看管理
    d3js path generator vs layouts
    d3js layout 深入理解
    RedisTemplate 事务处理方法 watch multi exec 的使用
    阻止联网
  • 原文地址:https://www.cnblogs.com/alice-bj/p/9252207.html
Copyright © 2020-2023  润新知