Restframework

1.restful規範

在以前的代碼習慣中,一般使用一個url對應一個視圖函數,如今有了restful規範,就要遵循。簡單來講,就是多個url對應一個視圖,視圖中封裝了get,post,put,patch,delete等主要方法。相對於FBV來講更加簡單,使用了CBV模式。前端

第一步:總體說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不一樣作不一樣操做。
    
    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/"
        }

2.初始drf

2.1簡單認識

2.1.1什麼是drf

drf是一個基於django開發的組件,本質是一個django的app。drf能夠幫咱們快速開發出一個遵循restful規範的程序。

2.1.2drf框架

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

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

2.1.3drf提供哪些功能

-視圖類  2個視圖基類(APIView,GenericAPIView)和一些其餘視圖類,幫助咱們完成一些功能。  
- 版本 "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
    "ALLOWED_VERSIONS": ['v1', 'v2'],
- 認證 經過生成隨機字符串保存在後端,前端攜帶與後端相比較,來實現認證 DEFAULT_AUTHENTICATION_CLASSES = ["類",] BaseAuthentication
- 權限 基於BasePermission
- 節流 throttle_classes = [AnonRateThrottle,]
-解析器,根據用戶請求體格式不一樣進行數據解析,解析以後放在request.data中。在進行解析時候,drf會讀取http請求頭 content-type. 若是content-type:x-www-urlencoded,那麼drf會根據 & 符號分割的形式去處理請 求體。 user=wang&age=19 若是content-type:application/json,那麼drf會根據 json 形式去處理請求體。 {"user":"wang","age":19}
-序列化,能夠對QuerySet進行序列化,也能夠對用戶提交的數據進行校驗
-篩選器
-分頁器 
-渲染器,能夠幫咱們把json數據渲染到頁面上進行友好的展現
Response

2.2使用

app註冊python

服務端會根據訪問者使用工具的不一樣來區分,若是不添加rest_framework,使用postman等工具訪問是沒有問題的,可是使用瀏覽器訪問會報錯,由於會默認找到rest_framework下的靜態html文件進行渲染,此時找不到,所以報錯。ajax

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework'
]

路由算法

from django.conf.urls import url
from django.contrib import admin
from api import views

urlpatterns = [
    url(r'^drf/info/', views.DrfInfoView.as_view()),
]

視圖數據庫

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

class DrfInfoView(APIView):

    def get(self,request,*args,**kwargs):
        data = {"":""}
        return Response(data)

2.3自定義序列化

路由django

from django.conf.urls import url
from django.contrib import admin
from api import views
urlpatterns = [
    url(r'^drf/category/$', views.DrfCategoryView.as_view()),  #get/post經過此url
    url(r'^drf/category/(?P<pk>\d+)/$', views.DrfCategoryView.as_view()), #put/patch/delete經過此url
]

視圖編程

from api import models
from django.forms.models import model_to_dict   #序列化,將model轉爲dict類型
class DrfCategoryView(APIView):
    def get(self,request,*args,**kwargs):
        """獲取全部文章分類/單個文章分類"""
        pk = kwargs.get('pk')
        if not pk:
            queryset = models.Category.objects.all().values('id','name')
            data_list = list(queryset)
            return Response(data_list)
        else:
            category_object = models.Category.objects.filter(id=pk).first()
            data = model_to_dict(category_object)
            return Response(data)
     def post(self,request,*args,**kwargs):
        """增長一條分類信息"""
        models.Category.objects.create(**request.data)
        return Response('成功')

3.drf序列化

https://www.cnblogs.com/wuzhengzheng/p/10411785.htmljson

3.1序列化

-序列化時instance=queryset,反序列化data參數寫爲data=request.data-進行序列化時,會將fields中全部字段進行序列化,反序列化時,也須要提交fields中的全部字段,若是不想提交某些字段,使用read_only參數。-在添加數據(post)請求時,能夠新建一個serializer,可能有一些不須要的字段。
source:能夠指定字段(name publish.name),能夠指定方法。
SerializerMethodField搭配方法使用(get_字段名字)
read_only:反序列化時,不傳
write_only:序列化時,不顯示

路由後端

from django.conf.urls import url
from django.contrib import admin
from api import views
urlpatterns = [
    url(r'^new/category/$', views.NewCategoryView.as_view()),
    url(r'^new/category/(?P<pk>\d+)/$', views.NewCategoryView.as_view()),
]

序列化

# ModelSerializer跟表模型綁定序列化
from app import models

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        # 指定表模型
        model = models.Book
        # 序列化全部的字段
        fields = '__all__'
        # 只想序列化title和id兩個字段
        # fields = ['title','id']
        # exclude 和 fields不要連用
        # excude = ['title]
        # depth深度,表示鏈表的深度
        #不建議使用:下幾層要取得參數不能控制,官方建議不要超過10,我的建議不超過3
        # depth = 1

    # publish = serializers.CharField(source='publish.name')
    # authors = serializers.SerializerMethodField()
    # def get_authors(self, obj):
    #     author_list = obj.authors.all()
    #     author_ser = AuthorSer(instance=author_list, many=True)
    #     return author_ser.data

    #爲書名增長自定義需求
    title = serializers.CharField(max_length=6,min_length=3,error_messages={'max_length':'太長了'})

    #也有局部鉤子函數
    def validate_title(self,value):
        from rest_framework import exceptions
        print(value)
        if value.startswith('tmd'):
            raise exceptions.ValidationError('不能以tmd開頭')
        return value

三個方式

要跨表查看其餘表中的字段,或者顯示models.CharField(choice=((1,"發佈"),(2,"刪除"))

class ArticleSerializer(serializers.ModelSerializer):
  #方式一:設置跨表深度
    depth=1
    #方式二:指定source
    category = serializers.CharField(source="category.name")
    status = serializers.CharField(source="get_status_display")
    #方式三,使用鉤子,和下面get_tags_txt結合使用
    tags_txt = serializers.SerializerMethodField(read_only=True) 
    class Meta:
        model = models.Article
        fields = ["id","title","summary","content","category","status","tags_txt"]
    def get_tags_txt(self,obj): #obj爲表對象
        tag_list = []
        for i in obj.tags.all():
            tag_list.append({"id":i.id,"name":i.name})
        return tag_list

查詢

class ArticleView(APIView):
    def get(self,request,*args,**kwargs):
        pk = kwargs.get("pk")
        if not pk: 
            queryset = models.Article.objects.all()
            ser = ArticleSerializer(instance=queryset,many=True) #查詢多個,many=True
            return Response(ser.data)
        else:
            queryset = models.Article.objects.filter(id=pk).first() 
            ser = ArticleSerializer(instance=queryset,many=False) #查詢一個,many=False
            return Response(ser.data)

新增

def post(self,request,*args,**kwargs):
        ser = ArticleSerializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response("添加成功!")
        return Response(ser.errors)

添加

def post(self,request,*args,**kwargs):
        ser = ArticleSerializer(data=request.data)
        ser_detail = ArticleDetailSerializer(data=request.data)
        if ser.is_valid() and ser_detail.is_valid():
            #前端提交時沒有author_id字段,在save時才添加,此處針對連表添加
            article_object = ser.save(author_id=1)  
            ser_detail.save(article=article_object)
            return Response('添加成功')
        return Response('錯誤')

修改

def put(self,request,*args,**kwargs):
        pk = kwargs.get("pk")
        article_object = models.Article.objects.filter(id=pk).first()
        ser = ArticleSerializer(instance=article_object,data=request.data)  #局部更新添加partial=True,全部要反序列化的字段都不是必填的
        if ser.is_valid():
            ser.save()
            return Response(ser.data)
        return Response(ser.errors)

3.2反序列化

在本身定義的類中重寫create方法

class BookSerializer(serializers.Serializer):
    ... ...
    def create(self, validated_data):
        res = models.Book.objects.create(**validated_data)
        return res

對應的view.py

class Books(APIView):    
    # 使用繼承了Serializers序列化類的對象,反序列化
    def post(self,request):
        response_dic = {'code': 100, 'msg': '添加成功!'}
        # 實例化產生一個序列化類的對象,data是要反序列化的字典
        booker = BookSerializer(data=request.data)
        if booker.is_valid():
            # 清洗經過的數據,經過create方法進行保存
            res = booker.create(booker.validated_data)
        return Response(response_dic)

4.篩選

url訪問

https://www.cnblogs.com/article/?category=1

自定義篩選功能MyFilterBackend

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/?category=1
        # models.News.objects.filter(category=1)
        # http://www.xx.com/cx/index/?category=1
        queryset = models.News.objects.all()
        obj = MyFilterBackend()
obj.filter_queryset(request,queryset,self)
    return Response('...')

視圖

#views.py中

from rest_framework.filters import BaseFilterBackend
#自定義篩選器
class MyFilterBackend(BaseFilterBackend):  
    def filter_queryset(self, request, queryset, view):
        val = request.query_params.get("category")
        return queryset.filter(category=val)

#視圖
class ArticleView(ListAPIView,CreateAPIView):
    queryset = models.Article.objects.all()
    serializer_class = ArticleSerials
    filter_backends = [MyFilterBackend,]

5.分頁

5.1介紹

drf默認集成了幾個分頁類

from rest_framework.pagination import BasePagination,PageNumberPagination,LimitOffsetPagination,CursorPagination

本身寫類繼承這些,只須要配置某些參數

class GoodsPagination(PageNumberPagination):
    """
    分頁
    """
    page_size = 10
    page_size_query_param = 'page_size'
    page_query_param = 'page'
    max_page_size = 100

5.2PageNumberPagination

url訪問

http:127.0.0.1:8000/?page=1

配置settings.py,在配置drf組件都要寫在REST_FRAMEWORK字典中

REST_FRAMEWORK = { "PAGE_SIZE":2 }

views中視圖函數

def get(self,request,*args,**kwargs):
        queryset = models.Article.objects.all()

        # 方式一:僅數據
        """
        page_object = PageNumberPagination()
        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})
        """
return Response(ser.data)

配置

1.settings中配置
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS":"rest_framework.pagination.PageNumberPagination",  #默認使用PagenumberPagination
"PAGE_SIZE":2,    #每頁顯示數量
}
2.views.py中
from rest_framework.pagination import PageNumberPagination
class ArticleView(ListAPIView,CreateAPIView):
    queryset = models.Article.objects.all()
    serializer_class = ArticleSerials
    pagination_class = PageNumberPagination

5.3LimitOffffsetPagination

url訪問

http://127.0.0.1:8000/?limit=4&offset=0

limit多少條 offset後面多少頁

from rest_framework.pagination import PageNumberPagination
from rest_framework.pagination import LimitOffsetPagination
from rest_framework import serializers
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)

6.視圖

2個視圖基類

6.1APIView

APIView是drf中全部視圖的基類,繼承自django的view類

APIView和view的不一樣之處:

傳入到視圖方法中的是REST framework的Request對象,而不是Django的HttpRequeset對象;
視圖方法能夠返回REST framework的Response對象,視圖會爲響應數據設置(render)符合前端要求的格式;
任何APIException異常都會被捕獲到,而且處理成合適的響應信息;
在進行dispatch()分發前,會對請求進行身份認證、權限檢查、流量控制。

支持定義的類屬性:

authentication_classes 列表或元祖,身份認證類
permissoin_classes 列表或元祖,權限檢查類
throttle_classes 列表或元祖,流量控制類

APIView中仍以常規的類視圖定義方法來實現get() 、post() 或者其餘請求方式的方法。

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

# url(r'^students/$', views.StudentsAPIView.as_view()),
class StudentsAPIView(APIView):
    def get(self, request):
        data_list = Student.objects.all()
        serializer = StudentModelSerializer(instance=data_list, many=True)
        return Response(serializer.data)

6.2GenericAPIView[通用視圖類]

繼承自APIView主要增長了操做序列化器和數據庫查詢的方法,做用是爲下面Mixin擴展類的執行提供方法支持。一般在使用時,可搭配一個或多個Mixin擴展類

支持定義的類屬性:

serializer_class 指明視圖使用的序列化器
queryset 指明使用的數據查詢集
pagination_class 指明分頁控制類
filter_backends 指明過濾控制後端

支持的方法:

get_serializer_class(self)
當出現一個視圖類中調用多個序列化器時,那麼能夠經過條件判斷在get_serializer_class方法中經過返回不一樣的序列化器類名就可讓視圖方法執行不一樣的序列化器對象了

get_serializer(self, args, *kwargs)
返回序列化器對象,主要用來提供給Mixin擴展類使用,若是咱們在視圖中想要獲取序列化器對象,也能夠直接調用此方法。

get_queryset(self)
返回視圖使用的查詢集,主要用來提供給Mixin擴展類使用,是列表視圖與詳情視圖獲取數據的基礎,默認返回queryset屬性

get_object(self)
返回詳情視圖所需的模型類數據對象,主要用來提供給Mixin擴展類使用

5個視圖擴展類

ListModelMixin
列表視圖擴展類,提供list(request, *args, **kwargs)方法快速實現列表視圖返回200狀態碼。
該Mixin的list方法會對數據進行過濾和分頁

CreateModelMixin
建立視圖擴展類,提供create(request, *args, **kwargs)方法快速實現建立資源的視圖,成功返回201狀態碼。
若是序列化器對前端發送的數據驗證失敗,返回400錯誤。

RetrieveModelMixin
詳情視圖擴展類,提供retrieve(request, *args, **kwargs)方法,能夠快速實現返回一個存在的數據對象。
若是存在,返回200, 不然返回404

UpdateModelMixin
更新視圖擴展類,提供update(request, *args, **kwargs)方法,能夠快速實現更新一個存在的數據對象。
同時也提供partial_update(request, *args, **kwargs)方法,能夠實現局部更新。
成功返回200,序列化器校驗數據失敗時,返回400錯誤

DestroyModelMixin
刪除視圖擴展類,提供destroy(request, *args, **kwargs)方法,能夠快速實現刪除一個存在的數據對象。
成功返回204,不存在返回404。

GenericAPIView的視圖子類

CreateAPIView
提供 post 方法
繼承自: GenericAPIView、CreateModelMixin

ListAPIView
提供 get 方法
繼承自:GenericAPIView、ListModelMixin

RetrieveAPIView
提供 get 方法
繼承自: GenericAPIView、RetrieveModelMixin

DestoryAPIView
提供 delete 方法
繼承自:GenericAPIView、DestoryModelMixin

UpdateAPIView
提供 put 和 patch 方法
繼承自:GenericAPIView、UpdateModelMixin

RetrieveUpdateAPIView
提供 get、put、patch方法
繼承自: GenericAPIView、RetrieveModelMixin、UpdateModelMixin

總體流程

1.發送get請求,會調用ListAPIView中的get方法
2.執行list方法,先去ArticleView中找,找不到,直到找到父類ListModelMixin中的list方法
3.queryset = self.filter_queryset(self.get_queryset())先找ArticleView中的filter_queryset和get_queryset()方法,找不到,直到找到GenericAPIView中有這2中方式,get_queryset()中queryset = self.queryset,從ArticleView中找self.queryset,找不到報錯,找到了將其queryset傳給此類中的queryset變量,而後將其值返回給filter_querset方法,最後將filter_queryset的返回值賦值給3步中的queryset,在list方法中執行序列化

img

使用

class TagSer(serializers.ModelSerializer):
    class Meta:
        model = models.Tag
        fields = "__all__"

class TagView(ListAPIView,CreateAPIView):
    queryset = models.Tag.objects.all()
    serializer_class = TagSer

    def get_serializer_class(self): #重寫GenericAPIView中的方法
        # self.request
        # self.args
        # self.kwargs
        if self.request.method == 'GET':
            return TagSer
        elif self.request.method == 'POST':
            return OtherTagSer
    def perform_create(self,serializer):
        serializer.save(author=1)

7.版本

使用思路

在視圖類中配置 versioning_class =注意這是單數形式,只能配置一個類
實現 determine_version 方法
全局配置 DEFAULT_VERSION ALLOWED_VERSIONS VERSION_PARAM

在 initial 中首先執行 determine_version,它裏面會生成獲取版本的對象以及版本。
獲取版本:request.version
獲取處理版本的對象:request.versioning_scheme
反向生成 url :url = request.versioning_scheme.reverse(viewname='<url 的別名>', request=request)

使用(局部)

url中寫version

url(r'^(?P<version>[v1|v2]+)/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):
  
      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中寫version

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配置

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'
  }

8.認證

8.1普通認證

先來登陸功能,登陸成功後將隨機字符串加入Userinfo表的token字段中

class UserInfo(models.Model):
    """ 用戶表 """
    username = models.CharField(verbose_name='用戶名',max_length=32)
    password = models.CharField(verbose_name='密碼',max_length=64)
    token = models.CharField(verbose_name='token',max_length=64,null=True,blank=True)

LoginView登陸視圖的實現

from rest_framework.views import APIView
from rest_framework.response import Response
from api import models
import uuid
class LoginView(APIView):
    """
    登陸接口
    """
    def post(self,request,*args,**kwargs):
        user_obj = models.UserInfo.objects.filter(**request.data).first()
        if not user_obj:
            return Response("登陸失敗!")
        random_string = str(uuid.uuid4())
        # 登陸成功後將token添加到數據庫,且返回給前端
        user_obj.token = random_string
        user_obj.save()
        return Response(random_string)

在以後寫的其餘視圖函數中,某些視圖函數須要驗證用戶是否登陸,就用到drf提供的BaseAuthentication認證功能。

先來單獨寫認證組件,須要用到的視圖函數直接調用便可

from rest_framework.authentication import BaseAuthentication
from api import models
class LuffyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get("token")
        if not token:
            return (None,None)
        user_obj = models.UserInfo.objects.filter(token=token).first()
        if user_obj:
            return (user_obj,token) #參數一給request.user,參數二給request.auth
        return (None,None)

須要用到認證功能的函數以下,調用方式以下:

from rest_framework.views import APIView
from rest_framework.response import Response
from api.extension.auth import LuffyAuthentication

class CommentView(APIView):
    #authentication_classes = [] 在settings中設置了全局認證,但此處不需,只需設置爲空列表便可
    authentication_classes = [LuffyAuthentication,]
    def get(self,request,*args,**kwargs):
        print(request.user)  #request.user獲得user object對象,只有request.user纔會調用認證
        print(request.auth)  #獲得token值
        return Response("獲取全部評論")
    def post(self,request,*args,**kwargs):
        if request.user:
            pass

若是你有100個視圖想要用認證組件,就須要在全局settings.py中設置了

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":["api.extension.auth.LuffyAuthentication",]
}

源碼分析(從認證功能函數開始)

1.當有請求發來時,會先執行APIView中的dispath方法,dispath方法中會執行initialize_request(request, *args, **kwargs),進行老的request的封裝。在封裝過程當中authenticators=self.get_authenticators(),此時執行get_authenticators(),進去此方法會看到return [auth() for auth in self.authentication_classes],由於本身寫的類中有authentication_class變量,此時authenticators=[LuffyAuthentication(),]

2.
當執行到get函數時,也是先進APIView的dispatch()方法,而後進入request的封裝函數中,查詢def user(self)方法,找到其中的self._authenticate()方法並執行,能夠看到 user_auth_tuple = authenticator.authenticate(self),執行本身定義組件中的authenticate方法,返回(a,b)元組,而後self.user, self.auth = 此元組,從而獲得request.user等於值1,request.auth等於值2

源碼大致流程

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

之後在視同中調用request.user

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

8.2jwt認證

8.2.1jwt實現原理及其優點

jwt的實現原理:
    - 用戶登陸成功以後,會給前端返回一段token。
    - token是由.分割的三段組成。
        - 第一段:類型和算法信心
        - 第二段:用戶信息+超時時間
        - 第三段:hs256(前兩段拼接)加密 + base64url
    - 之後前端再次發來信息時
        - 超時驗證
        - token合法性校驗
優點:
    - token只在前端保存,後端只負責校驗。
    - 內部集成了超時時間,後端能夠根據時間進行校驗是否超時。
    - 因爲內部存在hash256加密,因此用戶不能夠修改token,只要一修改就認證失敗。

8.2.2使用

app中註冊 rest_framework_jwt

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'
]

登陸視圖實現生成隨機字符串

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_jwt.settings import api_settings
from api import models
from rest_framework_jwt.views import VerifyJSONWebToken
from rest_framework.throttling import AnonRateThrottle,BaseThrottle
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({"code":10001,"error":"用戶名或密碼錯誤"})
        #
        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})

認證視圖

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)

settings配置(全局配置)

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":["api.extension.auth.HulaQueryParamAuthentication"],
}

若是不想使用此認證,在視圖函數中添加如下代碼

def get_authenticators(self):
    if self.request.method == "GET":
        return []
    elif self.request.method == "POST":
        return super().get_authenticators()

9.權限

要用權限須要drf中的BasePermission類,先來自定義權限組件

from rest_framework.permissions import BasePermission
from rest_framework import exceptions

class MyPermission(BasePermission):
    message = {'code': 10001, 'error': '你沒權限'} #has_permission返回false時會拋出此異常
    def has_permission(self, request, view): #針對全部請求
        if request.user:
            return True
        return False

    def has_object_permission(self, request, view, obj): #針對有pk值的請求(例RetrieveAPIView)
        return False

視圖函數中使用上定義的權限組件

from rest_framework.views import APIView
from rest_framework.response import Response
from api.extension.permission import LuffyPermission

class CommentView(APIView):
    permission_classes = [] #若是在settings設置了全局權限,但此視圖不想使用
    permission_classes = [LuffyPermission,]
    def get(self,request,*args,**kwargs):
        return Response("獲取全部評論")
    def post(self,request,*args,**kwargs):
        if request.user:
            pass

settings配置全局權限

REST_FRAMEWORK = {
    DEFAULT_PERMISSION_CLASSES:"api.extension.permission.LuffyPermission"
}

源碼分析

class APIView(View):
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    
    def dispatch(self, request, *args, **kwargs): #1執行dispath方法
       #2 封裝request對象
        self.initial(request, *args, **kwargs)
        經過反射執行視圖中的方法
    
    def initial(self, request, *args, **kwargs):
        
        # 權限判斷
        self.check_permissions(request)
    
    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')

源碼分析

1.請求過來,先走APIView中的dispatch方法,執行self.initialize_request()方法進行老的request的封裝,而後執行initial方法,進行認證和權限處理:
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
2.進入check_permissions方法:for循環遍歷本身定義的permission_classes,檢查每一個對象的has_permission()方法,若是返回false,拋出異常,返回true,繼續經過反射執行dispatch方法中的請求分發
for permission in self.get_permissions():
     if not permission.has_permission(request, self): 
          self.permission_denied(
              request, message=getattr(permission, 'message', None)
                )

10.頻率限制

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

  • 知識點

    {
      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.能夠訪問

如何實現頻率限制

匿名用戶:用ip做爲惟一標記(缺點:若是用戶使用代理ip,沒法作到真正的限制)
登陸用戶:用用戶名或者id做爲標識

具體實現

首先,django會在緩存中生成以下數據結構,用來存儲用戶訪問的時間戳
throttle_匿名標識(anon)_用戶ip:[時間戳1,時間戳2...]
每次用戶登陸時:
1.獲取當前時間戳
2.當前時間戳-60(如3/60s,一分鐘內3次),循環數據結構中時間戳B,將獲得值A和B比較,B<A,則刪除B
3.判斷數據結構中列表的個數,若是小於3,則能夠訪問

使用

#settings配置:
REST_FRAMEWORK={
"DEFAULT_THROTTLE_RATES":{
        "anon":'3/m' #一分鐘3次, 3/h 3/d 
    }
}

#views函數中使用
from rest_framework.throttling import AnonRateThrottle
class ArticleView(APIView):
    throttle_classes = [AnonRateThrottle,]

源碼剖析

請求發來,先執行dispatch方法,從dispatch方法中進入inital函數。前後執行到check_throttles()方法,在此看到,會執行對象的allow_request()方法
    def initial(self, request, *args, **kwargs):
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)
    def check_throttles(self, request):
        throttle_durations = []
        for throttle in self.get_throttles():
            if not throttle.allow_request(request, self):
                throttle_durations.append(throttle.wait())
def allow_request(self, request, view):
        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: #num_requests本身規定的訪問次數
            return self.throttle_failure()
        return self.throttle_success()

總結

# 如何實現的評率限制
- 匿名用戶,用IP做爲用戶惟一標記,但若是用戶換代理IP,沒法作到真正的限制。
- 登陸用戶,用用戶名或用戶ID作標識。
具體實現:
    在django的緩存中 = {
        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.能夠訪問

組件參考:https://www.jianshu.com/p/c16f8786e9f7

11.跨域

11.1跨域

因爲瀏覽器具備「同源策略」的限制,致使 `發送ajax請求` + `跨域` 存在沒法獲取數據。
若是在同一個域下發送ajax請求,瀏覽器的同源策略不會阻止。
若是在不一樣域下發送ajax,瀏覽器的同源策略會阻止。

總結

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

11.2解決跨域:CORS

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

from django.shortcuts import render,HttpResponse

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

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

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

簡單請求,發一次請求

設置響應頭就能夠解決
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
 
注意:同時知足以上兩個條件時,則是簡單請求,不然爲複雜請求

11.3.2總結

1. 因爲瀏覽器具備「同源策略」的限制,因此在瀏覽器上跨域發送Ajax請求時,會被瀏覽器阻止。
2. 解決跨域
   - 不跨域
   - CORS(跨站資源共享,本質是設置響應頭來解決)。
     - 簡單請求:發送一次請求
     - 複雜請求:發送兩次請求
相關文章
相關標籤/搜索