drf

DRF

什麼是DRF?

DRF從本質上來說, 它就是一個Django的App, 有了這樣一個App, 咱們就能夠更好的設計出符合RESTful規範的web應用 實際上, 即使沒有DRF, 咱們也可以自行設計出符合RESTful規範的web應用,是一個框架

爲何要使用DRF?

DRF就是這樣一個優秀的工具, 另外, 它不只可以幫助咱們快速的設計出符合RESTful規範的接口, 還提供了諸如 認證 , 權限 等等其餘強大的功能.使用了drf以後,能夠快速幫咱們開發restful規範來開發接口

drf組件的功能:
    + 根據請求方式不一樣作不一樣操做    get/post/put/patch/delete
    + 視圖,繼承APIView(在內部apiview繼承了django的View)
    + 解析器,解析請求體中的數據,將其變成咱們想要的格式。request.data,query_params
    + 序列化,對對象或對象列表(queryset)進行序列化操做以及表單驗證的功能。
    + 渲染器,渲染頁面
    


記憶:請求到來以後,先執行視圖的dispatch方法。

1. 視圖
2. 版本處理
3. 認證
4. 權限
5. 節流(頻率限制)
6. 解析器
7. 篩選器
8. 分頁
9. 序列化
10. 渲染

DRF的應用場景

參與先後端分離項目、參與爲app寫接口時,用drf會比較方便。

restful規範

第一步:總體說restful規範是什麼?
    接口的規範,規則,程序之間作數據交互遵循的規則

第二步:再詳細說restful建議
    1. https代替http,保證數據傳輸時安全。
    
    2. 在url中通常要體現api標識,這樣看到url就知道他是一個api。
        http://www.luffycity.com/api/....(建議,由於他不會存在跨域的問題)
        http://api.luffycity.com/....
        假設:
            前段:https://www.luffycity.com/home
            後端:https://www.luffycity.com/api/
                
    3. 在接口中要體現版本
        http://www.luffycity.com/api/v1....(建議,由於他不會存在跨域的問題)
        注意:版本還能夠放在請求頭中
            http://www.luffycity.com/api/
            accept: ...
            
    4. restful也稱爲面向資源編程,視網絡上的一切都是資源,對資源能夠進行操做,因此通常資源都用名詞。
        http://www.luffycity.com/api/user/
        
    5. 若是要加入一些篩選條件,能夠添加在url中    
        http://www.luffycity.com/api/user/?page=1&type=9

    6. 根據method不一樣作不一樣操做。get/post/delete/patch/put
    
    7. 返回給用戶狀態碼
        - 200,成功
        - 300,301永久 /302臨時
        - 400,403拒絕 /404找不到
        - 500,服務端代碼錯誤
        
        不少公司:
                def get(self,request,*args,**kwargs):
                    result = {'code':1000,'data':None,'error':None}
                    try:
                        val = int('你好')
                    except Exception as e:
                        result['code'] = 10001
                        result['error'] = '數據轉換錯誤'

                    return Response(result)
        
    8. 返回值
        GET http://www.luffycity.com/api/user/
            [
                {'id':1,'name':'alex','age':19},
                {'id':1,'name':'alex','age':19},
            ]
        POST http://www.luffycity.com/api/user/
            {'id':1,'name':'alex','age':19}
            
        GET http://www.luffycity.com/api/user/2/
            {'id':2,'name':'alex','age':19}
            
        PUT http://www.luffycity.com/api/user/2/
            {'id':2,'name':'alex','age':19}
        
        PATCH https//www.luffycity.com/api/user/2/
            {'id':2,'name':'alex','age':19}
            
        DELETE https//www.luffycity.com/api/user/2/
            空
            
    9. 操做異常時,要返回錯誤信息
    
        {
            error: "Invalid API key"
        }
    10. 對於下一個請求要返回一些接口:Hypermedia AP
        {
            'id':2,
            'name':'alex',
            'age':19,
            'depart': "http://www.luffycity.com/api/user/30/"
        }

安裝DRF

pip3 install djangorestframework

使用DRF步驟

0.設計表結構
from django.db import models
class Category(models.Model):
    """
    文章分類
    """
    name = models.CharField(verbose_name='分類',max_length=32)

class Article(models.Model):
    """
    文章表
    """
    title = models.CharField(verbose_name='標題',max_length=32)
    summary = models.CharField(verbose_name='簡介',max_length=255)
    content = models.TextField(verbose_name='文章內容')

1.註冊drf,在settings中INSTALLED_APPS註冊drf:
    'rest_framework'
2.寫路由
    from django.conf.urls import url
    from django.contrib import admin
    from drf_app import views
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        # 文章類型url
        url(r'^drf/categore/$', views.DrfCategoreView.as_view()),   #展現用
        url(r'^drf/categore/(?P<pk>\d+)/$', views.DrfCategoreView.as_view()),  #編輯刪除用
    ]
3.寫視圖
    from django.shortcuts import render 
    from drf_app import models
    from rest_framework.response import Response
    from rest_framework.views import APIView
    from django.forms import model_to_dict   
    

    # 文章類型
    class DrfCategoreView(APIView):   #繼承APIView
        #接口:實現訪問接口時,建立一個文章類型
        def post(self,request,*args,**kwargs):   #添加文章類型數據
            name = request.POST.get('name')      #獲取提交的內容,key_values
            category_obj = models.Category(
                name=name                        #建立文章類型數據
            )
            category_obj.save()       
            return Response('成功')              #給前端返回結果
        #接口:獲取全部文章類型
        def get(self,request,*args,**kwargs):    #獲取數據,在前端展現
            pk = kwargs.get('pk')                #獲取單條數據的p值k
            if not pk:                           #判斷pk知否存在
                obj = models.Article.objects.all().values()
                data = list(obj)
                return Response(data)            #Response只接受列表,字典,字符串類型的數據
            else:
                obj_dict = models.Article.objects.filter(pk=pk).first()
                data = model_to_dict(obj_dict)  #把對象轉換成字典
                return Response(data)
         #接口:更新文章類型
        def put(self,request,*args,**kwargs):     #更新數據
            pk = kwargs.get('pk')
            models.Category.objects.filter(id=pk).update(**request.data)  
                                            #request.data 返回解析以後的請求體數據
            return Response('更新成功')

        #接口:刪除文章類型
        def delete(self,request,*args,**kwargs):   #刪除數據
            pk = kwargs.get('pk')
            if not pk:
                return Response('刪除失敗,沒有該條數據')
            models.Article.objects.filter(pk=pk).delete()
            return Response('刪除成功')

DRF的request.data方法

request.data 返回解析以後的請求體數據
包含了解析以後的文件和非文件數據
包含了對POST、PUT、PATCH請求方式解析後的數據
利用了REST framework的parsers解析器,不只支持表單類型數據,也支持JSON數據

request.query_params.get('xx')相似於django的GET方法

DRF的序列化

基於上一個代碼添加校驗功能

from rest_framework import serializers
class NewCategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Category
        # fields = "__all__"
        fields = ['id','name']

class NewCategoryView(APIView):
    def get(self,request,*args,**kwargs):
        pk = kwargs.get('pk')
        if not pk:
            queryset = models.Category.objects.all()
            ser = NewCategorySerializer(instance=queryset,many=True)
            return Response(ser.data)
        else:
            model_object = models.Category.objects.filter(id=pk).first()
            ser = NewCategorySerializer(instance=model_object, many=False)
            return Response(ser.data)

    def post(self,request,*args,**kwargs):
        ser = NewCategorySerializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data)
        return Response(ser.errors)

    def put(self,request,*args,**kwargs):
        pk = kwargs.get('pk')
        category_object = models.Category.objects.filter(id=pk).first()
        ser = NewCategorySerializer(instance=category_object,data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data)
        return Response(ser.errors)

    def delete(self,request,*args,**kwargs):
        pk = kwargs.get('pk')
        models.Category.objects.filter(id=pk).delete()
        return Response('刪除成功')
    
   #局部更新
   def patch(self,request,*args,**kwargs):
        pk = kwargs.get('pk')
        article_obj = models.Article.objects.filter(pk=pk).first()
        ser = ArticleSerializer(instance=article_obj, data=request.data,partial=True)  # partial=True
        if ser.is_valid():
            ser.save()
            return Response('更新成功')
        return Response(ser.errors)

postman工具的安裝使用

安裝:

安裝:https://www.getpostman.com/

使用:

1572939531806

1572952711366

顯示關聯表的數據值

from rest_framework import serializers
from drf_app import models
# 文章的Serializer
class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Article
        fields = '__all__'     #拿到該表的所有字段
上述的代碼用的這種方法來展現獲取數據,所有字段但若是有關聯的表的字段,會顯示一下內容:
        {
        "id": 2,
        "status": 1,
        "title": "標題1",
        "summary": "簡介1",
        "content": "標題1內容",
        "category": 1
    },
    {
        "id": 3,
        "status": 1,
        "title": "標題3",
        "summary": "簡介3",
        "content": "標題3內容",
        "category": 2
    },
關聯的category表都爲數字,但想要他顯示該id對應的中文,須要對Serializer進行一些操做!
對ArticleSerializer進行操做!
有四種方法:
    1.  from rest_framework import serializers
        from drf_app import models
        # 文章的Serializer
        class ArticleSerializer(serializers.ModelSerializer):
            category = serializers.CharField(source='category.name')
            class Meta:
                model = models.Article
                fields = ['id','title','summary','content','category']
                
    2.  from rest_framework import serializers
        from drf_app import models
        # 文章的Serializer
        class ArticleSerializer(serializers.ModelSerializer):
      
            category = serializers.SerializerMethodField()
            class Meta:
                model = models.Article
              
                fields = ['id','title','summary','content','category']

            def get_category(self,obj):   #obj是表中的一行記錄的對象
                return obj.category.name
            
            
      3.能夠直接進行篩選值:
        article_obj = models.Article.objects.all().values('刷選想要的數據,能夠進行跨表查')
      
      4.
        from rest_framework import serializers
        from drf_app import models
        # 文章的Serializer
        class ArticleSerializer(serializers.ModelSerializer):
   
        class Meta:
            model = models.Article
            fields = '__all__'
            depth = 1   #會把關聯的表的數據所有拿出來,能夠理解爲與當前的表關聯表的個數
    {
        "id": 2,
        "status": 1,
        "title": "標題1",
        "summary": "簡介1",
        "content": "標題1內容",
        "category": {
            "id": 1,
            "name": "廣告"
        }
    },
    {
        "id": 3,
        "status": 1,
        "title": "標題3",
        "summary": "簡介3",
        "content": "標題3內容",
        "category": {
            "id": 2,
            "name": "it"
        }
    },

 推薦前兩種!!!!!!!!!!

顯示choice類型字段的對應數據

1.
from rest_framework import serializers
from drf_app import models
# 文章的Serializer
class ArticleSerializer(serializers.ModelSerializer):
    category = serializers.CharField(source='category.name')   
    status = serializers.CharField(source='get_status_display')   # get_status_display方法,不用加括號,drf中會檢查get_status_display是否能夠執行,自動會加括號執行
    class Meta:
        model = models.Article
        fields = ['id','title','summary','content','category','status']
       
        
2.
from rest_framework import serializers
from drf_app import models
# 文章的Serializer
class ArticleSerializer(serializers.ModelSerializer):
    category = serializers.SerializerMethodField()
    class Meta:
        model = models.Article
        fields = ['id','title','summary','content','category','status']
        def get_category(self,obj):   #obj是表中的一行記錄的對象
            return get_status_display()

DRF分頁

方式一:PageNumberPagination

針對於每頁固定顯示數據個數,在訪問路徑須要加:?page=xx,看第幾頁的數據前端

# settings.py
#定義每頁顯示的數據個數
REST_FRAMEWORK = {
    'PAGE_SIZE': 2
}  
# serializer.py
class PageArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Article
        fields = "__all__"

# view.py
from drf_app.serializer import PageArticleSerializer
from rest_framework.pagination import PageNumberPagination
class PageArticleView(APIView):
  def get(self,request,*args,**kwargs):        
    queryset = models.Article.objects.all()
        # 方式一:僅數據 
        # 分頁對象        
        page_object = PageNumberPagination()        
        # 調用 分頁對象.paginate_queryset方法進行分頁,獲得的結果是分頁以後的數據       
        # result就是分完頁的一部分數據        
        result = page_object.paginate_queryset(queryset,request,self)        
        # 序列化分頁以後的數據       
        ser = PageArticleSerializer(instance=result,many=True)       
            return Response(ser.data)        
        
        # 方式二:數據 + 分頁信息        
        page_object = PageNumberPagination()     
        result = page_object.paginate_queryset(queryset, request, self)    
        ser = PageArticleSerializer(instance=result, many=True)        
            return page_object.get_paginated_response(ser.data)  
        
        # 方式三:數據 + 部分分頁信息
        page_object = PageNumberPagination()        
        result = page_object.paginate_queryset(queryset, request, self)        
        ser = PageArticleSerializer(instance=result, many=True)        
            return Response({'count':page_object.page.paginator.count,'result':ser.data})

方式二: LimitOffsetPagination

針對於每頁顯示個數不固定,在訪問路徑上加/?offset=xx$limit=xxpython

from rest_framework.pagination import PageNumberPagination
from rest_framework.pagination import LimitOffsetPagination 
from rest_framework import serializers
class PageArticleSerializer(serializers.ModelSerializer):    
    class Meta:        
        model = models.Article     
         fields = "__all__"
class HulaLimitOffsetPagination(LimitOffsetPagination):    
    max_limit = 2     
class PageArticleView(APIView):   
    def get(self,request,*args,**kwargs):      
    queryset = models.Article.objects.all()
    page_object = HulaLimitOffsetPagination()        
    result = page_object.paginate_queryset(queryset, request, self)        
    ser = PageArticleSerializer(instance=result, many=True)        
    return Response(ser.data)

分頁擴展:

# url
url(r'^page/view/article/$', views.PageViewArticleView.as_view()),

# view.py
from rest_framework.generics import ListAPIView
class PageViewArticleSerializer(serializers.ModelSerializer):    
    class Meta:        
        model = models.Article       
         fields = "__all__"
class PageViewArticleView(ListAPIView):    
    queryset = models.Article.objects.all()    
    serializer_class = PageViewArticleSerializer
# 源碼裏調用執行,在settings中配置用哪一種分頁方法
# settings.py
REST_FRAMEWORK = {    
    "PAGE_SIZE":2,   
    "DEFAULT_PAGINATION_CLASS":"rest_framework.pagination.PageNumberPagination"
}

篩選

class ArticleView(APIView):    
""" 文章視圖類 """
    def get(self,request,*args,**kwargs):        
        """ 獲取文章列表 """        
        pk = kwargs.get('pk')        
        if not pk:            
            condition = {}            
            category = request.query_params.get('category')           
            if category:                
                condition['category'] = category            
                queryset = models.Article.objects.filter(**condition).order_by('date')                              pager = PageNumberPagination()            
                result = pager.paginate_queryset(queryset,request,self)           
                ser = ArticleListSerializer(instance=result,many=True)            
                return Response(ser.data)        
            article_object = models.Article.objects.filter(id=pk).first()        
            ser = PageArticleSerializer(instance=article_object,many=False)        
            return Response(ser.data)

DRF內置組件篩選

from django.shortcuts import render 
from rest_framework.views import APIView 
from rest_framework.response import Response 
from . import models
from rest_framework.filters import BaseFilterBackend
class MyFilterBackend(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):        
        val = request.query_params.get('cagetory')        
        return queryset.filter(category_id=val)    
class IndexView(APIView):
    def get(self,request,*args,**kwargs):        
        # http://www.xx.com/cx/index/        
        # models.News.objects.all()
        # http://www.xx.com/cx/index/?category=1        
        # models.News.objects.filter(category=1)
        # http://www.xx.com/cx/index/?category=1       
        # queryset = models.News.objects.all()        
        # obj = MyFilterBackend()        
        # result = obj.filter_queryset(request,queryset,self)        
        # print(result)                
        return Response('...')

DRF視圖

class TagSer(serializers.ModelSerializer):
    category = serializers.CharField(source="get_category_display")
    author = serializers.SerializerMethodField()
    class Meta:
        model = models.Article
        fields = "__all__"
    def get_author(self,obj):
        return obj.author.username


class AddTagSer(serializers.ModelSerializer):
    class Meta:
        model = models.Article
        fields = "__all__"


# generics的展現,添加
class NewArticleView(ListAPIView,CreateAPIView):
    queryset = models.Article.objects.all()   #提供所有數據
    serializer_class = TagSer                 #提供序列化器,源碼中的serializer_class爲None

    def get_serializer_class(self):           #源碼中提供的方法 返回值爲return self.serializer_class,因此能夠自定義serializer_class一個序列化器
        if self.request.method == 'GET':
            return TagSer 

        elif self.request.method == 'POST':
            return AddTagSer

    def perform_create(self,serializer):
        serializer.save(author_id=2)          #多添加一個存儲數據,關聯的數據

# generics的更新,刪除
class OldArticleView(DestroyAPIView,UpdateAPIView,RetrieveAPIView):
    queryset = models.Article.objects.all()
    serializer_class = TagSer

    
    
ListAPIView:展現       執行get --->list
CreateAPIView:添加     執行perform_create方法
DestroyAPIView:刪除    執行delete方法
UpdateAPIView:更新     執行perform_update方法
RetrieveAPIView:展現單條數據,不能和ListAPIView同用,由於都有get方法 , 執行retrieve方法 
    instance = self.get_object()獲取的單條數據
    
當GenericAPIView提供的方法不能知足要求是,須要自定義方法

drf視圖源碼大體流程

# GenericAPIView提供了一些方法: 取數據,篩選,分頁,驗證等方法,充當一個橋樑的做用
    get_object
    get_serializer--->self.get_serializer_class()
    get_queryset---->self.queryset
    filter_queryset--->self.filter_backends
    paginator_queryset--->self.pagination_class()


# ListAPIView源碼,當發送一個請求時(get請求)源碼的執行流程,一個get請求
class NewsView(ListAPIView):
    queryset = models.News.objects.all()
    filter_backends = [NewFilterBackend, ]
    serializer_class = NewSerializers
    pagination_class = PageNumberPagination
    
1.去NewsView找get方法 
def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)
2.執行self.list方法,本類中沒有該方法,去父類中找list方法,執行list方法:
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)
3.
先執行list中的get_queryset()方法,首先去本身的類中找get_queryset()方法,沒有就去父類中找get_queryset(self)方法,
調用了一個self.queryset,先去本身的類中找,本身的類中定義了queryset,接着執行list中filter_queryset方法,
本身的類中沒有該方法,去父類中找,父類中沒有再去另外一個父類中找,去GenericAPIView中找filter_queryset方法,
在執行循環filter_backends方法,本身有定義filter_backends,是一個列表,循環中實例化序列化器,在執行filter_queryset方法

DRF版本控制

第一步:寫路由url(r'^api/(P<version>\w+)/user/$',views.UserView.as_view()),
第二步:寫模塊導入from rest_framework.versioning import URLPathVersioning
第三步:寫視圖 可不寫
request.version獲取版本號
class UserView(APIView):  # DEFAULT_VERSIONING_CLASS在APIView中默認配置
    def get(self,request,*args,**kwargs):
        print(request.version)
        return Response('....')
第四步:寫settings配置:
REST_FRAMEWORK = {
    "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",  #配置全局的版本信息
    "ALLOWED_VERSIONS":['v1','v2']    #配置容許版本號範圍

}

源碼

class APIView(View):
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
    
    def dispatch(self, request, *args, **kwargs):
       
        # ###################### 第一步 ###########################
        """
        request,是django的request,它的內部有:request.GET/request.POST/request.method
        args,kwargs是在路由中匹配到的參數,如:
            url(r'^order/(\d+)/(?P<version>\w+)/$', views.OrderView.as_view()),
            http://www.xxx.com/order/1/v2/
        """
        self.args = args
        self.kwargs = kwargs

        """
        request = 生成了一個新的request對象,此對象的內部封裝了一些值。
        request = Request(request)
            - 內部封裝了 _request = 老的request
        """
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request

        self.headers = self.default_response_headers  # deprecate?

        try:
            # ###################### 第二步 ###########################
            self.initial(request, *args, **kwargs)

            執行視圖函數。。
    
    def initial(self, request, *args, **kwargs):
       
        # ############### 2.1 處理drf的版本 ##############
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme
        ...
        
    def determine_version(self, request, *args, **kwargs):
        if self.versioning_class is None:
            return (None, None)
        scheme = self.versioning_class() # obj = XXXXXXXXXXXX()
        return (scheme.determine_version(request, *args, **kwargs), scheme)
        
class OrderView(APIView):
    versioning_class = URLPathVersioning
    def get(self,request,*args,**kwargs):
        print(request.version)
        print(request.versioning_scheme)
        return Response('...')

    def post(self,request,*args,**kwargs):
        return Response('post')
class URLPathVersioning(BaseVersioning):
    """
    urlpatterns = [
        url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'),
       
    ]
    """
    invalid_version_message = _('Invalid version in URL path.')

    def determine_version(self, request, *args, **kwargs):
        version = kwargs.get(self.version_param, self.default_version)
        if version is None:
            version = self.default_version

        if not self.is_allowed_version(version):
            raise exceptions.NotFound(self.invalid_version_message)
        return version

使用(局部)

  • url中寫versionnginx

    url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'),
  • 在視圖中應用web

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.request import Request
    from rest_framework.versioning import URLPathVersioning
    
    
    class OrderView(APIView):
    
        versioning_class = URLPathVersioning
        def get(self,request,*args,**kwargs):
            print(request.version)
            print(request.versioning_scheme)
            return Response('...')
    
        def post(self,request,*args,**kwargs):
            return Response('post')
  • 在settings中配置面試

    REST_FRAMEWORK = {
        "PAGE_SIZE":2,
        "DEFAULT_PAGINATION_CLASS":"rest_framework.pagination.PageNumberPagination",
        "ALLOWED_VERSIONS":['v1','v2'],
        'VERSION_PARAM':'version'
    }

使用(全局)推薦

  • url中寫versionajax

    url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'),
    
    url(r'^(?P<version>\w+)/users/$', users_list, name='users-list'),
  • 在視圖中應用數據庫

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.request import Request
    from rest_framework.versioning import URLPathVersioning
    
    
    class OrderView(APIView):
        def get(self,request,*args,**kwargs):
            print(request.version)
            print(request.versioning_scheme)
            return Response('...')
    
        def post(self,request,*args,**kwargs):
            return Response('post')
  • 在settings中配置django

    REST_FRAMEWORK = {
        "PAGE_SIZE":2,
        "DEFAULT_PAGINATION_CLASS":"rest_framework.pagination.PageNumberPagination",
        "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",
        "ALLOWED_VERSIONS":['v1','v2'],
        'VERSION_PARAM':'version'
    }

DRF認證

認證(面試)

from django.conf.urls import url,include
from django.contrib import admin
from . import views
urlpatterns = [
    url(r'^login/$', views.LoginView.as_view()),
    url(r'^order/$', views.OrderView.as_view()),
    url(r'^user/$', views.UserView.as_view()),
]
import uuid
from django.shortcuts import render
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from rest_framework.versioning import URLPathVersioning
from rest_framework.views import APIView
from rest_framework.response import Response

from . import models

class LoginView(APIView):

    def post(self,request,*args,**kwargs):
        user_object = models.UserInfo.objects.filter(**request.data).first()
        if not user_object:
            return Response('登陸失敗')
        random_string = str(uuid.uuid4())
        user_object.token = random_string
        user_object.save()
        return Response(random_string)

class MyAuthentication:
    def authenticate(self, request):
        """
        Authenticate the request and return a two-tuple of (user, token).
        """
        token = request.query_params.get('token')
        user_object = models.UserInfo.objects.filter(token=token).first()
        if user_object:
            return (user_object,token)
        return (None,None)

class OrderView(APIView):
    authentication_classes = [MyAuthentication, ]
    def get(self,request,*args,**kwargs):
        print(request.user)
        print(request.auth)
        return Response('order')

class UserView(APIView):
    authentication_classes = [MyAuthentication,]
    def get(self,request,*args,**kwargs):
        print(request.user)
        print(request.auth)
        return Response('user')

源碼分析

class Request:

    def __init__(self, request,authenticators=None):
        self._request = request
        self.authenticators = authenticators or ()
        
    @property
    def user(self):
        """
        Returns the user associated with the current request, as authenticated
        by the authentication classes provided to the request.
        """
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._user
    
    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        for authenticator in self.authenticators:  #認證的對象列表[TokenAuthentication,]
            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._not_authenticated()
        
    @user.setter
    def user(self, value):
        """
        Sets the user on the current request. This is necessary to maintain
        compatibility with django.contrib.auth where the user property is
        set in the login and logout functions.

        Note that we also set the user on Django's underlying `HttpRequest`
        instance, ensuring that it is available to any middleware in the stack.
        """
        self._user = value
        self._request.user = value
class APIView(View):
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    
    def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch,
        but with extra hooks for startup, finalize, and exception handling.
        """
        # ###################### 第一步 ###########################
        """
        request,是django的request,它的內部有:request.GET/request.POST/request.method
        args,kwargs是在路由中匹配到的參數,如:
            url(r'^order/(\d+)/(?P<version>\w+)/$', views.OrderView.as_view()),
            http://www.xxx.com/order/1/v2/
        """
        self.args = args
        self.kwargs = kwargs


        """
        request = 生成了一個新的request對象,此對象的內部封裝了一些值。
        request = Request(request)
            - 內部封裝了 _request = 老的request
            - 內部封裝了 authenticators = [MyAuthentication(), ]
        """
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
    
    def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)

        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(), # [MyAuthentication(),]
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

    def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        return [ auth() for auth in self.authentication_classes ]
    # 執行上一個代碼塊的class Request,判斷user
    # 執行上一個代碼塊的class Request,判斷user
    # 執行上一個代碼塊的class Request,判斷user
    
    
class LoginView(APIView):
    authentication_classes = []
    def post(self,request,*args,**kwargs):
        user_object = models.UserInfo.objects.filter(**request.data).first()
        if not user_object:
            return Response('登陸失敗')
        random_string = str(uuid.uuid4())
        user_object.token = random_string
        user_object.save()
        return Response(random_string)

class OrderView(APIView):
    # authentication_classes = [TokenAuthentication, ]
    def get(self,request,*args,**kwargs):
        print(request.user)
        print(request.auth)
        if request.user:
            return Response('order')
        return Response('滾')

class UserView(APIView):
    同上

總結

當用戶發來請求時,找到認證的全部類並實例化成爲對象列表,而後將對象列表封裝到新的request對象中。

之後在視同中調用request.user

在內部會循環認證的對象列表,並執行每一個對象的authenticate方法,該方法用於認證,他會返回兩個值分別會賦值給
request.user/request.auth

DRF權限

權限

使用

也能夠把權限全局應用,添加到settings中編程

from rest_framework.permissions import BasePermission
from rest_framework import exceptions

class MyPermission(BasePermission):
    message = {'code': 10001, 'error': '你沒權限'}   #沒有權限的報錯信息
    def has_permission(self, request, view):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        if request.user:   
            return True

        # raise exceptions.PermissionDenied({'code': 10001, 'error': '你沒權限'})
        return False

    def has_object_permission(self, request, view, obj):   #使用單條數據的時候
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return False
class OrderView(APIView):
    permission_classes = [MyPermission,]    #在這個視圖中添加權限
    # permission_classes = []  不須要加權限就把改列表設置爲空
    def get(self,request,*args,**kwargs):
        return Response('order')


class UserView(APIView):
    permission_classes = [MyPermission, ]
    def get(self,request,*args,**kwargs):
        return Response('user')
REST_FRAMEWORK = {
    "PAGE_SIZE":2,
    "DEFAULT_PAGINATION_CLASS":"rest_framework.pagination.PageNumberPagination",
    "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",
    "ALLOWED_VERSIONS":['v1','v2'],
    'VERSION_PARAM':'version',
    "DEFAULT_AUTHENTICATION_CLASSES":["kka.auth.TokenAuthentication",] 
}

在同一個視圖中對不一樣的功能進行權限

方式一:
class MyPermission(BasePermission):
    message = '你沒有權限'
    def has_permission(self, request, view):  #獲取多個對象的時候執行
        if request.method == "GET":   #對用戶請求進行權限添加,當用戶的請求爲get方法,就不加權限,不然就添加權限
            return True   
        else:

            if request.user and request.auth: #有權限
                print(request.user)
                return True
            return False #無權限

    def has_object_permission(self, request, view, obj): #獲取單個對象的時候執行
        return self.has_permission(request, view)
    
# 其他的同上
方式二:
# 加裝飾器

from functools import update_wrapper

# 同一個視圖中對不一樣功能添加權限的裝飾器
def wrap_permission(*permissions, validate_permission=True):
    """custom permissions for special route"""
    def decorator(func):
        def wrapper(self, request, *args, **kwargs):
            self.permission_classes = permissions
            if validate_permission:
                self.check_permissions(request)
            return func(self, request, *args, **kwargs)
        return update_wrapper(wrapper, func)
    return decorator

def permission_classes(permission_classes):
    def decorator(func):
        func.permission_classes = permission_classes
        return func
    return decorator
class HandleArticleView(UpdateAPIView,DestroyAPIView,RetrieveAPIView):
    
    queryset = models.Article.objects.all()
    serializer_class = DetailArticleSerializer

    @wrap_permission(MyPermission)   # 只對刪除添加權限
    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return Response('刪除成功')

源碼分析

class APIView(View):
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    
    def dispatch(self, request, *args, **kwargs):
        封裝request對象
        self.initial(request, *args, **kwargs)
        經過反射執行視圖中的方法
    # 認證已經執行完了,request已經封裝,接着向下執行initial
    def initial(self, request, *args, **kwargs):
        版本的處理
        # 認證
        self.perform_authentication(request)
        
        # 權限判斷
        self.check_permissions(request)
        
        
        self.check_throttles(request)
        
    def perform_authentication(self, request):
        request.user
    
    def check_permissions(self, request):
        # [對象,對象,]
        for permission in self.get_permissions():
            if not permission.has_permission(request, self):
                self.permission_denied(request, message=getattr(permission, 'message', None))
    def permission_denied(self, request, message=None):
        if request.authenticators and not request.successful_authenticator:
            raise exceptions.NotAuthenticated()
        raise exceptions.PermissionDenied(detail=message)
        
    def get_permissions(self):
        return [permission() for permission in self.permission_classes] #權限對象列表
    
###
class UserView(APIView):
    permission_classes = [MyPermission, ]
    
    def get(self,request,*args,**kwargs):
        return Response('user')

同源跨域

跨域

跨域:因爲瀏覽器具備「同源策略」的限制, 當一個請求url的協議、域名、端口三者之間任意一個與當前頁面url不一樣即爲跨域json

因爲瀏覽器具備「同源策略」的限制。
    若是在同一個域下發送ajax請求,瀏覽器的同源策略不會阻止。
    若是在不一樣域下發送ajax,瀏覽器的同源策略會阻止。

限制ajax請求
    一個域名兩個端口訪問時就得加端口,IP:端口

舉例:

訪問http:/www.xxxx.com,返回一個頁面,頁面上有按鈕這個按鈕能夠發送ajax請求到其餘網站獲取數據,請求的網站是http:/api.xxxx.com. 這個網站和http:/www.xxxx.com不是同一個域,能夠送出請求,訪問的網站也有響應給瀏覽器,但瀏覽器有同源策略,當訪問走到nginx的時候,就會報錯,


+ 域相同,永遠不會存在跨域。
  + crm,非先後端分離,沒有跨域。
  + 路飛學城,先後端分離,沒有跨域(以前有,如今沒有)。
+ 域不一樣時,纔會存在跨域。
  + 拉勾網,先後端分離,存在跨域(設置響應頭解決跨域)

解決跨域:CORS

本質在數據返回值設置響應頭

from django.shortcuts import render,HttpResponse

def json(request):
    response = HttpResponse("JSONasdfasdf")
    response['Access-Control-Allow-Origin'] = "*"
    return response

跨域時,發送了2次請求?

在跨域時,發送的請求會分爲兩種:

  • 簡單請求,發一次請求。

    設置響應頭就能夠解決
    from django.shortcuts import render,HttpResponse
    
    def json(request):
        response = HttpResponse("JSONasdfasdf")
        response['Access-Control-Allow-Origin'] = "*"
        return response
  • 複雜請求,發兩次請求。

    • 預檢

    • 請求

    @csrf_exempt
    def put_json(request):
        response = HttpResponse("JSON複雜請求")
        if request.method == 'OPTIONS':
            # 處理預檢
            response['Access-Control-Allow-Origin'] = "*"
            response['Access-Control-Allow-Methods'] = "PUT"
            return response
        elif request.method == "PUT":
            return response
條件:
    一、請求方式:HEAD、GET、POST
    二、請求頭信息:
        Accept
        Accept-Language
        Content-Language
        Last-Event-ID
        Content-Type 對應的值是如下三個中的任意一個
                                application/x-www-form-urlencoded
                                multipart/form-data
                                text/plain
 
注意:同時知足以上兩個條件時,則是簡單請求,不然爲複雜請求

DRF的訪問頻率限制

  • 頻率限制在認證、權限以後

  • 知識點

    {
      throttle_anon_1.1.1.1:[100121340,],
      1.1.1.2:[100121251,100120450,]
    }
    
    
    限制:60s能訪問3次
    來訪問時:
      1.獲取當前時間 100121280
      2.100121280-60 = 100121220,小於100121220全部記錄刪除
      3.判斷1分鐘之內已經訪問多少次了? 4 
      4.沒法訪問
    停一會
    來訪問時:
      1.獲取當前時間 100121340
      2.100121340-60 = 100121280,小於100121280全部記錄刪除
      3.判斷1分鐘之內已經訪問多少次了? 0
      4.能夠訪問

源碼

from rest_framework.views import APIView
from rest_framework.response import Response

from rest_framework.throttling import AnonRateThrottle,BaseThrottle

class ArticleView(APIView):
    throttle_classes = [AnonRateThrottle,]
    def get(self,request,*args,**kwargs):
        return Response('文章列表')

class ArticleDetailView(APIView):
    def get(self,request,*args,**kwargs):
        return Response('文章列表')
class BaseThrottle:
    """
    Rate throttling of requests.
    """

    def allow_request(self, request, view):
        """
        Return `True` if the request should be allowed, `False` otherwise.
        """
        raise NotImplementedError('.allow_request() must be overridden')

    def get_ident(self, request):
        """
        Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR
        if present and number of proxies is > 0. If not use all of
        HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR.
        """
        xff = request.META.get('HTTP_X_FORWARDED_FOR')
        remote_addr = request.META.get('REMOTE_ADDR')
        num_proxies = api_settings.NUM_PROXIES

        if num_proxies is not None:
            if num_proxies == 0 or xff is None:
                return remote_addr
            addrs = xff.split(',')
            client_addr = addrs[-min(num_proxies, len(addrs))]
            return client_addr.strip()

        return ''.join(xff.split()) if xff else remote_addr

    def wait(self):
        """
        Optionally, return a recommended number of seconds to wait before
        the next request.
        """
        return None


class SimpleRateThrottle(BaseThrottle):
    """
    A simple cache implementation, that only requires `.get_cache_key()`
    to be overridden.

    The rate (requests / seconds) is set by a `rate` attribute on the View
    class.  The attribute is a string of the form 'number_of_requests/period'.

    Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day')

    Previous request information used for throttling is stored in the cache.
    """
    cache = default_cache
    timer = time.time
    cache_format = 'throttle_%(scope)s_%(ident)s'
    scope = None
    THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES

    def __init__(self):
        if not getattr(self, 'rate', None):
            self.rate = self.get_rate()
        self.num_requests, self.duration = self.parse_rate(self.rate)

    def get_cache_key(self, request, view):
        """
        Should return a unique cache-key which can be used for throttling.
        Must be overridden.

        May return `None` if the request should not be throttled.
        """
        raise NotImplementedError('.get_cache_key() must be overridden')

    def get_rate(self):
        """
        Determine the string representation of the allowed request rate.
        """
        if not getattr(self, 'scope', None):
            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
                   self.__class__.__name__)
            raise ImproperlyConfigured(msg)

        try:
            return self.THROTTLE_RATES[self.scope]
        except KeyError:
            msg = "No default throttle rate set for '%s' scope" % self.scope
            raise ImproperlyConfigured(msg)

    def parse_rate(self, rate):
        """
        Given the request rate string, return a two tuple of:
        <allowed number of requests>, <period of time in seconds>
        """
        if rate is None:
            return (None, None)
        num, period = rate.split('/')
        num_requests = int(num)
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
        return (num_requests, duration)

    def allow_request(self, request, view):
        """
        Implement the check to see if the request should be throttled.

        On success calls `throttle_success`.
        On failure calls `throttle_failure`.
        """
        if self.rate is None:
            return True

        # 獲取請求用戶的IP
        self.key = self.get_cache_key(request, view)
        if self.key is None:
            return True

        # 根據IP獲取他的全部訪問記錄,[]
        self.history = self.cache.get(self.key, [])

        self.now = self.timer()

        # Drop any requests from the history which have now passed the
        # throttle duration
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        if len(self.history) >= self.num_requests:
            return self.throttle_failure()
        return self.throttle_success()

    def throttle_success(self):
        """
        Inserts the current request's timestamp along with the key
        into the cache.
        """
        self.history.insert(0, self.now)
        self.cache.set(self.key, self.history, self.duration)
        return True

    def throttle_failure(self):
        """
        Called when a request to the API has failed due to throttling.
        """
        return False

    def wait(self):
        """
        Returns the recommended next request time in seconds.
        """
        if self.history:
            remaining_duration = self.duration - (self.now - self.history[-1])
        else:
            remaining_duration = self.duration

        available_requests = self.num_requests - len(self.history) + 1
        if available_requests <= 0:
            return None

        return remaining_duration / float(available_requests)


class AnonRateThrottle(SimpleRateThrottle):
    """
    Limits the rate of API calls that may be made by a anonymous users.

    The IP address of the request will be used as the unique cache key.
    """
    scope = 'anon'

    def get_cache_key(self, request, view):
        if request.user.is_authenticated:
            return None  # Only throttle unauthenticated requests.

        return self.cache_format % {
            'scope': self.scope,
            'ident': self.get_ident(request)
        }

自定義頻率限制

1573526307873

總結

  1. 如何實現的頻率限制

    - 匿名用戶,用IP做爲用戶惟一標記,但若是用戶換代理IP,沒法作到真正的限制。
    - 登陸用戶,用用戶名或用戶ID作標識。
    具體實現:
     在django的緩存中 = {
            throttle_anon_1.1.1.1:[100121340,],
            1.1.1.2:[100121251,100120450,]
        }
    
    
    DRF中的頻率控制基本原理是基於訪問次數和時間的,固然咱們能夠經過本身定義的方法來實現。
    
    當咱們請求進來,走到咱們頻率組件的時候,DRF內部會有一個字典來記錄訪問者的IP,
    
    以這個訪問者的IP爲key,value爲一個列表,存放訪問者每次訪問的時間,
    
    { IP1: [第三次訪問時間,第二次訪問時間,第一次訪問時間],}
    
    把每次訪問最新時間放入列表的最前面,記錄這樣一個數據結構後,經過什麼方式限流呢~~
    
    若是咱們設置的是10秒內只能訪問5次,
    
      -- 1,判斷訪問者的IP是否在這個請求IP的字典裏
    
      -- 2,保證這個列表裏都是最近10秒內的訪問的時間
    
          判斷當前請求時間和列表裏最先的(也就是最後的)請求時間的差
    
          若是差大於10秒,說明請求以及不是最近10秒內的,刪除掉,
    
          繼續判斷倒數第二個,直到差值小於10秒
    
      -- 3,判斷列表的長度(即訪問次數),是否大於咱們設置的5次,
    
          若是大於就限流,不然放行,並把時間放入列表的最前面

jwt

基本原理:

1573518094244

用於在先後端分離時,實現用戶認證相關的一項技術。

通常用戶認證有2種方式:

  • token

    用戶登陸成功以後,生成一個隨機字符串,本身保留一分+給前端返回一份。
    
    之後前端再來發請求時,須要攜帶字符串。
    後端對字符串進行校驗。
  • jwt

    用戶登陸成功以後,生成一個隨機字符串,給前端。
      - 生成隨機字符串
          {typ:"jwt","alg":'HS256'}     {id:1,username:'alx','exp':10}
          98qow39df0lj980945lkdjflo.saueoja8979284sdfsdf.asiuokjd978928374
          - 類型信息經過base64加密
          - 數據經過base64加密
          - 兩個密文拼接在h256加密+加鹽
      - 給前端返回
          98qow39df0lj980945lkdjflo.saueoja8979284sdfsdf.asiuokjd978928375
    
    前端獲取隨機字符串以後,保留起來。
    之後再來發送請求時,攜帶98qow39df0lj980945lkdjflo.saueoja8979284sdfsdf.asiuokjd978928375。
    
    
    後端接受到以後,
      1.先作時間判斷
        2.字符串合法性校驗。

安裝

pip3 install djangorestframework-jwt

案例

  • app中註冊

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'api.apps.ApiConfig',
        'rest_framework',
        'rest_framework_jwt'
    ]
  • 用戶登陸

    import uuid
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.versioning import URLPathVersioning
    from rest_framework import status
    
    from api import models
    
    class LoginView(APIView):
        """
        登陸接口
        """
        def post(self,request,*args,**kwargs):
    
            # 基於jwt的認證
            # 1.去數據庫獲取用戶信息
            from rest_framework_jwt.settings import api_settings
            jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
            jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
    
            user = models.UserInfo.objects.filter(**request.data).first()
            if not user:
                return Response({'code':1000,'error':'用戶名或密碼錯誤'})
    
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            return Response({'code':1001,'data':token})
  • 用戶認證

    from rest_framework.views import APIView
    from rest_framework.response import Response
    
    # from rest_framework.throttling import AnonRateThrottle,BaseThrottle
    
    
    class ArticleView(APIView):
        # throttle_classes = [AnonRateThrottle,]
    
        def get(self,request,*args,**kwargs):
            # 獲取用戶提交的token,進行一步一步校驗
            import jwt
            from rest_framework import exceptions
            from rest_framework_jwt.settings import api_settings
            jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
    
            jwt_value = request.query_params.get('token')
            try:
                payload = jwt_decode_handler(jwt_value)
            except jwt.ExpiredSignature:
                msg = '簽名已過時'
                raise exceptions.AuthenticationFailed(msg)
            except jwt.DecodeError:
                msg = '認證失敗'
                raise exceptions.AuthenticationFailed(msg)
            except jwt.InvalidTokenError:
                raise exceptions.AuthenticationFailed()
            print(payload)
    
            return Response('文章列表')

    使用

    舉例

    有文章,評論,訂單視圖,當匿名用戶訪問時文章能夠訪問,評論能夠訪問,添加評論須要認證,訂單所有要認證,而且給訪問文章頁面加頻率限制

    # url.py
    
    from django.conf.urls import url
    from .views import account
    from .views import article
    from .views import comment
    from .views import order
    
    urlpatterns = [
        url(r'^login/$',account.LoginView.as_view()),
        url(r'^article/$',article.ArticleAPIView.as_view()),
        url(r'^comment/$',comment.CommentAPIView.as_view()),
        url(r'^order/$',order.OrderAPIView.as_view()),
    
    ]
    # account.py
    
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from api import models
    from rest_framework_jwt.settings import api_settings
    
    class LoginView(APIView):
        authentication_classes = []
        def post(self,request,*args,**kwargs):
            user = models.UserInfo.objects.filter(username=request.data.get('username'),password=request.data.get('password')).first()
            if not user:
                return Response('用戶名或密碼錯誤')
            jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
            payload = jwt_payload_handler(user)
    
            jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
            token= jwt_encode_handler(payload)
            return Response({'code':10000,'data':token})
    # auth.py
    import jwt
    from rest_framework import exceptions
    from rest_framework.authentication import BaseAuthentication
    from rest_framework_jwt.settings import api_settings
    from api import models
    
    class HulaQueryParamAuthentication(BaseAuthentication):
        def authenticate(self, request):
            """
            # raise Exception(), 不在繼續往下執行,直接返回給用戶。
            # return None ,本次認證完成,執行下一個認證
            # return ('x',"x"),認證成功,不須要再繼續執行其餘認證了,繼續日後權限、節流、視圖函數
            """
            token = request.query_params.get('token')
            if not token:
                raise exceptions.AuthenticationFailed({'code':10002,'error':"登陸成功以後才能操做"})
    
            jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
            try:
                payload = jwt_decode_handler(token)
            except jwt.ExpiredSignature:
                raise exceptions.AuthenticationFailed({'code':10003,'error':"token已過時"})
            except jwt.DecodeError:
                raise exceptions.AuthenticationFailed({'code':10004,'error':"token格式錯誤"})
            except jwt.InvalidTokenError:
                raise exceptions.AuthenticationFailed({'code':10005,'error':"認證失敗"})
    
            jwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLER
            username = jwt_get_username_from_payload(payload)
            user_object = models.UserInfo.objects.filter(username=username).first()
            return (user_object,token)
    # article.py , comment.py , order.py
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.throttling import AnonRateThrottle #頻率
    
    #文章
    class ArticleAPIView(APIView):
        throttle_classes = [AnonRateThrottle, ]   #頻率
    
        def get(self,request,*args,**kwargs):
    
            return Response('文章列表')
    
        def get_authenticators(self):
            if self.request.method == 'GET':
                return [] #不須要認證就加[]
    
    # 評論
    class CommentAPIView(APIView):
    
        def get(self,request,*args,**kwargs):
            return Response('評論列表')
    
        def post(self,request,*args,**kwargs):
            return Response('添加評論')
    
        def get_authenticators(self):
            if self.request.method == 'GET':
                return []
            elif self.request.method == 'POST':
                return super().get_authenticators()  #執行父類的get_authenticators()會在settings中找DEFAULT_AUTHENTICATION_CLASSES
    
     # 訂單
     class OrderAPIView(APIView):
    
        def get(self, request, *args, **kwargs):
            return Response('訂單列表')
    
        def post(self, request, *args, **kwargs):
            return Response('添加訂單')

```

# settings.py
import datetime
REST_FRAMEWORK = {
    "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",
    "ALLOWED_VERSIONS":['v1','v2'],#版本
    "DEFAULT_AUTHENTICATION_CLASSES":['api.extensions.auth.HulaQueryParamAuthentication',],
    "DEFAULT_THROTTLE_RATES":{
        "anon":'10/m' #1分鐘訪問3次
    }
}

JWT_AUTH={
    'JWT_EXPIRATION_DELTA': datetime.timedelta(minutes=10), #token過時時間
}
相關文章
相關標籤/搜索