主頁 > 後端開發 > DjangoRESTframework

DjangoRESTframework

2020-09-15 22:05:18 後端開發

目錄

  • 1. DjangoRESTframework
    • 1.1 web應用模式
      • 1 前后端不分離
      • 2 前后端分離
    • 1.1 認識RESTful
    • 1.2 使用Django開發REST 介面
    • 1.3 Django REST framework 簡介
  • 2. DRF工程搭建
    • 2.1 環境安裝與配置
    • 2.2 使用
  • 3. Serializer序列化器
    • 3.1 定義Serializer
      • 1. 定義方法
      • 2. 欄位與選項
        • 通用引數:
      • 3. 創建Serializer物件
    • 3.2 序列化使用
      • 1 基本使用
      • 2 關聯物件嵌套序列化
        • 1) PrimaryKeyRelatedField
        • 2) StringRelatedField
        • 3)使用關聯物件的序列化器
        • many引數
    • 3.3 反序列化使用
      • 1. 驗證
        • 1)validate_
        • 2)validate
        • 3)validators
      • 2. 保存
        • 說明:
    • 3.4 模型類序列化器ModelSerializer
      • 1. 定義
      • 2. 指定欄位
      • 3. 添加額外引數
  • 4. 視圖
    • 4.1 Request 與 Response
      • 1. Request
      • 常用屬性
        • 1).data
        • 2).query_params
      • 2. Response
      • 構造方式
      • 3. 狀態碼
        • 1)資訊告知 - 1xx
        • 2)成功 - 2xx
        • 3)重定向 - 3xx
        • 4)客戶端錯誤 - 4xx
        • 5)服務器錯誤 - 5xx
    • 4.2 視圖概覽
    • 4.3 視圖說明
      • 1. 兩個基類
        • 1)APIView
          • 支持定義的屬性:
        • 2)GenericAPIView
          • 支持定義的屬性:
          • 提供的方法:
          • get_serializer(self, *args, **kwargs)
      • 2. 五個擴展類
        • 1)ListModelMixin
        • 2)CreateModelMixin
        • 3) RetrieveModelMixin
        • 4)UpdateModelMixin
        • 5)DestroyModelMixin
      • 3. 幾個可用子類視圖
        • 1) CreateAPIView
        • 2)ListAPIView
        • 3)RetireveAPIView
        • 4)DestoryAPIView
        • 5)UpdateAPIView
        • 6)RetrieveUpdateAPIView
        • 7)RetrieveUpdateDestoryAPIView
    • 4.4 視圖集ViewSet
      • 1. action屬性
      • 2. 常用視圖集父類
        • 1) ViewSet
        • 2)GenericViewSet
        • 3)ModelViewSet
        • 4)ReadOnlyModelViewSet
      • 3. 視圖集中定義附加action動作
      • 4. 視圖集的繼承關系
    • 4.5 路由Routers
      • 1. 使用方法
      • 2. 視圖集中包含附加action的
      • 3. 路由router形成URL的方式
  • 5. 其他功能
    • 5.1 版本
    • 5.2 認證Authentication
    • 5.3 權限Permissions
      • 使用
      • 提供的權限
      • 舉例
    • 5.4 限流Throttling
      • 使用
      • 可選限流類
      • 實體
    • 5.5 過濾Filtering
    • 5.6 排序
      • 使用方法:
    • 5.7 分頁Pagination
      • 可選分頁器
        • 1) PageNumberPagination
        • 2)LimitOffsetPagination
    • 5.78 例外處理 Exceptions
      • REST framework定義的例外
    • 5.9 自動生成介面檔案
      • 1. 安裝依賴
      • 2. 設定介面檔案訪問路徑
      • 3. 檔案描述說明的定義位置
      • 4. 訪問介面檔案網頁
        • 兩點說明:
    • 5.10 JWT
      • 1 介紹
      • 2 安裝
      • 3 案例
    • 5.11 跨域
      • 1 解決跨域:CORS
      • 2 跨域時,發送了2次請求?
      • 3 總結
    • 5.12 paramiko

1. DjangoRESTframework

1.1 web應用模式

在開發Web應用中,有兩種應用模式:

  • 前后端不分離
  • 前后端分離

1 前后端不分離

前后端不分離

在前后端不分離的應用模式中,前端頁面看到的效果都是由后端控制,由后端渲染頁面或重定向,也就是后端需要控制前端的展示,前端與后端的耦合度很高,

這種應用模式比較適合純網頁應用,但是當后端對接App時,App可能并不需要后端回傳一個HTML網頁,而僅僅是資料本身,所以后端原本回傳網頁的介面不再適用于前端App應用,為了對接App后端還需再開發一套介面,

2 前后端分離

前后端分離

在前后端分離的應用模式中,后端僅回傳前端所需的資料,不再渲染HTML頁面,不再控制前端的效果,至于前端用戶看到什么效果,從后端請求的資料如何加載到前端中,都由前端自己決定,網頁有網頁的處理方式,App有App的處理方式,但無論哪種前端,所需的資料基本相同,后端僅需開發一套邏輯對外提供資料即可,

在前后端分離的應用模式中 ,前端與后端的耦合度相對較低,

在前后端分離的應用模式中,我們通常將后端開發的每個視圖都稱為一個介面,或者API,前端通過訪問介面來對資料進行增刪改查,

1.1 認識RESTful

1.給別人提供一個URL,根據URL請求方式的不同,做不同操作,
	get,獲取
	post,增加
	put,全部更新
	patch,區域更新
	delete,洗掉
2.資料傳輸基于json格式,

1.2 使用Django開發REST 介面

Django框架中使用的圖書英雄案例來寫一套支持圖書資料增刪改查的REST API介面,

在此案例中,前后端均發送JSON格式資料,

# models
from django.db import models

class BookInfo(models.Model):
    btitle = models.CharField(max_length=20, verbose_name='名稱')
    bpub_date = models.DateField(verbose_name='發布日期', null=True)
    bread = models.IntegerField(default=0, verbose_name='閱讀量')
    bcomment = models.IntegerField(default=0, verbose_name='評論量')
    image = models.ImageField(upload_to='booktest', verbose_name='圖片', null=True)
# urls.py
urlpatterns = [
    url(r'^books/$', views.BooksAPIVIew.as_view()),
    url(r'^books/(?P<pk>\d+)/$', views.BookAPIView.as_view())
]
# views.py

from datetime import datetime

class BooksAPIVIew(View):
    """
    查詢所有圖書、增加圖書
    """
    def get(self, request):
        """
        查詢所有圖書
        路由:GET /books/
        """
        queryset = BookInfo.objects.all()
        book_list = []
        for book in queryset:
            book_list.append({
                'id': book.id,
                'btitle': book.btitle,
                'bpub_date': book.bpub_date,
                'bread': book.bread,
                'bcomment': book.bcomment,
                'image': book.image.url if book.image else ''
            })
        return JsonResponse(book_list, safe=False)

    def post(self, request):
        """
        新增圖書
        路由:POST /books/ 
        """
        json_bytes = request.body
        json_str = json_bytes.decode()
        book_dict = json.loads(json_str)

        # 此處詳細的校驗引數省略

        book = BookInfo.objects.create(
            btitle=book_dict.get('btitle'),
            bpub_date=datetime.strptime(book_dict.get('bpub_date'), '%Y-%m-%d').date()
        )

        return JsonResponse({
            'id': book.id,
            'btitle': book.btitle,
            'bpub_date': book.bpub_date,
            'bread': book.bread,
            'bcomment': book.bcomment,
            'image': book.image if book.image else ''
        }, status=201)


class BookAPIView(View):
    def get(self, request, pk):
        """
        獲取單個圖書資訊
        路由: GET  /books/<pk>/
        """
        try:
            book = BookInfo.objects.get(pk=pk)
        except BookInfo.DoesNotExist:
            return HttpResponse(status=404)

        return JsonResponse({
            'id': book.id,
            'btitle': book.btitle,
            'bpub_date': book.bpub_date,
            'bread': book.bread,
            'bcomment': book.bcomment,
            'image': book.image.url if book.image else ''
        })

    def put(self, request, pk):
        """
        修改圖書資訊
        路由: PUT  /books/<pk>
        """
        try:
            book = BookInfo.objects.get(pk=pk)
        except BookInfo.DoesNotExist:
            return HttpResponse(status=404)

        json_bytes = request.body
        json_str = json_bytes.decode()
        book_dict = json.loads(json_str)

        # 此處詳細的校驗引數省略

        book.btitle = book_dict.get('btitle')
        book.bpub_date = datetime.strptime(book_dict.get('bpub_date'), '%Y-%m-%d').date()
        book.save()

        return JsonResponse({
            'id': book.id,
            'btitle': book.btitle,
            'bpub_date': book.bpub_date,
            'bread': book.bread,
            'bcomment': book.bcomment,
            'image': book.image.url if book.image else ''
        })

    def delete(self, request, pk):
        """
        洗掉圖書
        路由: DELETE /books/<pk>/
        """
        try:
            book = BookInfo.objects.get(pk=pk)
        except BookInfo.DoesNotExist:
            return HttpResponse(status=404)

        book.delete()

        return HttpResponse(status=204)

使用Postman測驗上述介面

1) 獲取所有圖書資料

GET 方式訪問 http://127.0.0.1:8000/books/, 回傳狀態碼200,資料如下

[
    {
        "id": 1,
        "btitle": "射雕英雄傳",
        "bpub_date": "1980-05-01",
        "bread": 12,
        "bcomment": 34,
        "image": ""
    },
    {
        "id": 2,
        "btitle": "天龍八部",
        "bpub_date": "1986-07-24",
        "bread": 36,
        "bcomment": 40,
        "image": ""
    }
]

2)獲取單一圖書資料

GET 訪問 http://127.0.0.1:8000/books/5/ ,回傳狀態碼200, 資料如下

{
    "id": 5,
    "btitle": "西游記",
    "bpub_date": "1988-01-01",
    "bread": 10,
    "bcomment": 10,
    "image": "booktest/xiyouji.png"
}

3)新增圖書資料

POST 訪問http://127.0.0.1:8000/books/,發送JSON資料:

{
    "btitle": "三國演義",
    "bpub_date": "1990-02-03"
}

回傳狀態碼201,資料如下

{
    "id": 8,
    "btitle": "三國演義",
    "bpub_date": "1990-02-03",
    "bread": 0,
    "bcomment": 0,
    "image": ""
}

4)修改圖書資料

PUT 訪問http://127.0.0.1:8000/books/8/,發送JSON資料:

{
    "btitle": "三國演義(第二版)",
    "bpub_date": "1990-02-03"
}

回傳狀態碼200,資料如下

{
    "id": 8,
    "btitle": "三國演義(第二版)",
    "bpub_date": "1990-02-03",
    "bread": 0,
    "bcomment": 0,
    "image": ""
}

5)洗掉圖書資料

DELETE 訪問http://127.0.0.1:8000/books/8/,回傳204狀態碼

1.3 Django REST framework 簡介

  1. 在序列化與反序列化時,雖然操作的資料不盡相同,但是執行的程序卻是相似的,也就是說這部分代碼是可以復用簡化撰寫的,
  2. 在開發REST API的視圖中,雖然每個視圖具體操作的資料不同,但增、刪、改、查的實作流程基本套路化,所以這部分代碼也是可以復用簡化撰寫的:
    • :校驗請求資料 -> 執行反序列化程序 -> 保存資料庫 -> 將保存的物件序列化并回傳
    • :判斷要洗掉的資料是否存在 -> 執行資料庫洗掉
    • :判斷要修改的資料是否存在 -> 校驗請求的資料 -> 執行反序列化程序 -> 保存資料庫 -> 將保存的物件序列化并回傳
    • :查詢資料庫 -> 將資料序列化并回傳

2. DRF工程搭建

記憶:請求到來之后,先執行視圖的dispatch方法,
免除csrf認證
1. 視圖(APIView、ListAPIView、ListModelMinx)
2. 版本處理
3. 認證
4. 權限
5. 節流(頻率限制)
6. 決議器
7. 篩選器
8. 分頁
9. 序列化
10. 渲染

2.1 環境安裝與配置

DRF需要以下依賴:

  • Python (2.7, 3.2, 3.3, 3.4, 3.5, 3.6)
  • Django (1.10, 1.11, 2.0)

DRF是以Django擴展應用的方式提供的

pip install djangorestframework -i https:
//pypi.douban.com/simple/

2.2 使用

  • 注冊:在settings.pyINSTALLED_APPS中添加'rest_framework',

    INSTALLED_APPS = [
        'rest_framework'
    ]
    

3. Serializer序列化器

序列化器的作用:

  1. 進行資料的校驗
  2. 對資料物件進行轉換

3.1 定義Serializer

1. 定義方法

繼承自rest_framework.serializers.Serializer,

資料庫模型類BookInfo

class BookInfo(models.Model):
    btitle = models.CharField(max_length=20, verbose_name='名稱')
    bpub_date = models.DateField(verbose_name='發布日期', null=True)
    bread = models.IntegerField(default=0, verbose_name='閱讀量')
    bcomment = models.IntegerField(default=0, verbose_name='評論量')
    image = models.ImageField(upload_to='booktest', verbose_name='圖片', null=True)

我們想為這個模型類提供一個序列化器,可以定義如下:

class BookInfoSerializer(serializers.Serializer):
    """圖書資料序列化器"""
    id = serializers.IntegerField(label='ID', read_only=True)
    btitle = serializers.CharField(label='名稱', max_length=20)
    bpub_date = serializers.DateField(label='發布日期', required=False)
    bread = serializers.IntegerField(label='閱讀量', required=False)
    bcomment = serializers.IntegerField(label='評論量', required=False)
    image = serializers.ImageField(label='圖片', required=False)

注意:serializer不是只能為資料庫模型類定義,也可以為非資料庫模型類的資料定義,serializer是獨立于資料庫之外的存在,

2. 欄位與選項

常用欄位型別

欄位 欄位構造方式
BooleanField BooleanField()
NullBooleanField NullBooleanField()
CharField CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)
EmailField EmailField(max_length=None, min_length=None, allow_blank=False)
RegexField RegexField(regex, max_length=None, min_length=None, allow_blank=False)
SlugField SlugField(maxlength=50, min_length=None, allow_blank=False) 正則欄位,驗證正則模式 [a-zA-Z0-9-]+
URLField URLField(max_length=200, min_length=None, allow_blank=False)
UUIDField UUIDField(format='hex_verbose') format: 1) 'hex_verbose'"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 2) 'hex'"5ce0e9a55ffa654bcee01238041fb31a" 3)'int' - 如: "123456789012312313134124512351145145114" 4)'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
IPAddressField IPAddressField(protocol='both', unpack_ipv4=False, **options)
IntegerField IntegerField(max_value=https://www.cnblogs.com/LYPZX/p/None, min_value=None)
FloatField FloatField(max_value=https://www.cnblogs.com/LYPZX/p/None, min_value=None)
DecimalField DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=https://www.cnblogs.com/LYPZX/p/None, min_value=None) max_digits: 最多位數 decimal_palces: 小數點位置
DateTimeField DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)
DateField DateField(format=api_settings.DATE_FORMAT, input_formats=None)
TimeField TimeField(format=api_settings.TIME_FORMAT, input_formats=None)
DurationField DurationField()
ChoiceField ChoiceField(choices) choices與Django的用法相同
MultipleChoiceField MultipleChoiceField(choices)
FileField FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
ImageField ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
ListField ListField(child=, min_length=None, max_length=None)
DictField DictField(child=)

選項引數:

引數名稱 作用
max_length 最大長度
min_lenght 最小長度
allow_blank 是否允許為空
trim_whitespace 是否截斷空白字符
max_value 最小值
min_value 最大值

通用引數:

引數名稱 說明
read_only 表明該欄位僅用于序列化輸出,默認False
write_only 表明該欄位僅用于反序列化輸入,默認False
required 表明該欄位在反序列化時必須輸入,默認True
default 反序列化時使用的默認值
allow_null 表明該欄位是否允許傳入None,默認False
validators 該欄位使用的驗證器
error_messages 包含錯誤編號與錯誤資訊的字典
label 用于HTML展示API頁面時,顯示的欄位名稱
help_text 用于HTML展示API頁面時,顯示的欄位幫助提示資訊

3. 創建Serializer物件

定義好Serializer類后,就可以創建Serializer物件了,

Serializer的構造方法為:

Serializer(instance=None, data=https://www.cnblogs.com/LYPZX/p/empty, **kwarg)

說明:

1)用于序列化時,將模型類物件傳入instance引數

2)用于反序列化時,將要被反序列化的資料傳入data引數

3)除了instance和data引數外,在構造Serializer物件時,還可通過context引數額外添加資料,如

serializer = AccountSerializer(account, context={'request': request})

通過context引數附加的資料,可以通過Serializer物件的context屬性獲取,

3.2 序列化使用

我們在django shell中來學習序列化器的使用,

python manage.py shell

1 基本使用

1) 先查詢出一個圖書物件

from booktest.models import BookInfo

book = BookInfo.objects.get(id=2)

2) 構造序列化器物件

from booktest.serializers import BookInfoSerializer

serializer = BookInfoSerializer(book)

3)獲取序列化資料

通過data屬性可以獲取序列化后的資料

serializer.data
# {'id': 2, 'btitle': '天龍八部', 'bpub_date': '1986-07-24', 'bread': 36, 'bcomment': 40, 'image': None}

4)如果要被序列化的是包含多條資料的查詢集QuerySet,可以通過添加many=True引數補充說明

book_qs = BookInfo.objects.all()
serializer = BookInfoSerializer(book_qs, many=True)
serializer.data
# [OrderedDict([('id', 2), ('btitle', '天龍八部'), ('bpub_date', '1986-07-24'), ('bread', 36), ('bcomment', 40), ('image', N]), OrderedDict([('id', 3), ('btitle', '笑傲江湖'), ('bpub_date', '1995-12-24'), ('bread', 20), ('bcomment', 80), ('image'ne)]), OrderedDict([('id', 4), ('btitle', '雪山飛狐'), ('bpub_date', '1987-11-11'), ('bread', 58), ('bcomment', 24), ('ima None)]), OrderedDict([('id', 5), ('btitle', '西游記'), ('bpub_date', '1988-01-01'), ('bread', 10), ('bcomment', 10), ('im', 'booktest/xiyouji.png')])]

2 關聯物件嵌套序列化

如果需要序列化的資料中包含有其他關聯物件,則對關聯物件資料的序列化需要指明,

例如,在定義英雄資料的序列化器時,外鍵hbook(即所屬的圖書)欄位如何序列化?

我們先定義HeroInfoSerialzier除外鍵欄位外的其他部分:

class HeroInfoSerializer(serializers.Serializer):
    """英雄資料序列化器"""
    GENDER_CHOICES = (
        (0, 'male'),
        (1, 'female')
    )
    id = serializers.IntegerField(label='ID', read_only=True)
    hname = serializers.CharField(label='名字', max_length=20)
    hgender = serializers.ChoiceField(choices=GENDER_CHOICES, label='性別', required=False)
    hcomment = serializers.CharField(label='描述資訊', max_length=200, required=False, allow_null=True)

對于關聯欄位,可以采用以下幾種方式:

1) PrimaryKeyRelatedField

此欄位將被序列化為關聯物件的主鍵,

hbook = serializers.PrimaryKeyRelatedField(label='圖書', read_only=True)

指明欄位時需要包含read_only=True或者queryset引數:

  • 包含read_only=True引數時,該欄位將不能用作反序列化使用

使用效果:

from booktest.serializers import HeroInfoSerializer
from booktest.models import HeroInfo
hero = HeroInfo.objects.get(id=6)
serializer = HeroInfoSerializer(hero)
serializer.data
# {'id': 6, 'hname': '喬峰', 'hgender': 1, 'hcomment': '降龍十八掌', 'hbook': 2}

2) StringRelatedField

此欄位將被序列化為關聯物件的字串表示方式(即__str__方法的回傳值)

hbook = serializers.StringRelatedField(label='圖書')

使用效果

{'id': 6, 'hname': '喬峰', 'hgender': 1, 'hcomment': '降龍十八掌', 'hbook': '天龍八部'}

3)使用關聯物件的序列化器

hbook = BookInfoSerializer()

使用效果

{'id': 6, 'hname': '喬峰', 'hgender': 1, 'hcomment': '降龍十八掌', 'hbook': OrderedDict([('id', 2), ('btitle', '天龍八部')te', '1986-07-24'), ('bread', 36), ('bcomment', 40), ('image', None)])}

many引數

如果關聯的物件資料不是只有一個,而是包含多個資料,如想序列化圖書BookInfo資料,每個BookInfo物件關聯的英雄HeroInfo物件可能有多個,此時關聯欄位型別的指明仍可使用上述幾種方式,只是在宣告關聯欄位時,多補充一個many=True引數即可,

此處僅拿PrimaryKeyRelatedField型別來舉例,其他相同,

在BookInfoSerializer中添加關聯欄位:

class BookInfoSerializer(serializers.Serializer):
    """圖書資料序列化器"""
    id = serializers.IntegerField(label='ID', read_only=True)
    btitle = serializers.CharField(label='名稱', max_length=20)
    bpub_date = serializers.DateField(label='發布日期', required=False)
    bread = serializers.IntegerField(label='閱讀量', required=False)
    bcomment = serializers.IntegerField(label='評論量', required=False)
    image = serializers.ImageField(label='圖片', required=False)
    heroinfo_set = serializers.PrimaryKeyRelatedField(read_only=True, many=True)  # 新增

使用效果:

from booktest.serializers import BookInfoSerializer
from booktest.models import BookInfo
book = BookInfo.objects.get(id=2)
serializer = BookInfoSerializer(book)
serializer.data
# {'id': 2, 'btitle': '天龍八部', 'bpub_date': '1986-07-24', 'bread': 36, 'bcomment': 40, 'image': None, 'heroinfo_set': [6,8, 9]}

3.3 反序列化使用

1. 驗證

使用序列化器進行反序列化時,需要對資料進行驗證后,才能獲取驗證成功的資料或保存成模型類物件,

在獲取反序列化的資料前,必須呼叫is_valid()方法進行驗證,驗證成功回傳True,否則回傳False,

驗證失敗,可以通過序列化器物件的errors屬性獲取錯誤資訊,回傳字典,包含了欄位和欄位的錯誤,如果是非欄位錯誤,可以通過修改REST framework配置中的NON_FIELD_ERRORS_KEY來控制錯誤字典中的鍵名,

驗證成功,可以通過序列化器物件的validated_data屬性獲取資料,

在定義序列化器時,指明每個欄位的序列化型別和選項引數,本身就是一種驗證行為,

如我們前面定義過的BookInfoSerializer

class BookInfoSerializer(serializers.Serializer):
    """圖書資料序列化器"""
    id = serializers.IntegerField(label='ID', read_only=True)
    btitle = serializers.CharField(label='名稱', max_length=20)
    bpub_date = serializers.DateField(label='發布日期', required=False)
    bread = serializers.IntegerField(label='閱讀量', required=False)
    bcomment = serializers.IntegerField(label='評論量', required=False)
    image = serializers.ImageField(label='圖片', required=False)

通過構造序列化器物件,并將要反序列化的資料傳遞給data構造引數,進而進行驗證

from booktest.serializers import BookInfoSerializer
data = https://www.cnblogs.com/LYPZX/p/{'bpub_date': 123}
serializer = BookInfoSerializer(data=https://www.cnblogs.com/LYPZX/p/data)
serializer.is_valid()  # 回傳False
serializer.errors
# {'btitle': [ErrorDetail(string='This field is required.', code='required')], 'bpub_date': [ErrorDetail(string='Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]].', code='invalid')]}
serializer.validated_data  # {}

data = https://www.cnblogs.com/LYPZX/p/{'btitle': 'python'}
serializer = BookInfoSerializer(data=https://www.cnblogs.com/LYPZX/p/data)
serializer.is_valid()  # True
serializer.errors  # {}
serializer.validated_data  #  OrderedDict([('btitle', 'python')])

is_valid()方法還可以在驗證失敗時拋出例外serializers.ValidationError,可以通過傳遞raise_exception=True引數開啟,REST framework接收到此例外,會向前端回傳HTTP 400 Bad Request回應,

# Return a 400 response if the data was invalid.
serializer.is_valid(raise_exception=True)

如果覺得這些還不夠,需要再補充定義驗證行為,可以使用以下三種方法:

1)validate_<field_name>

<field_name>欄位進行驗證,如

class BookInfoSerializer(serializers.Serializer):
    """圖書資料序列化器"""
    ...

    def validate_btitle(self, value):
        if 'django' not in value.lower():
            raise serializers.ValidationError("圖書不是關于Django的")
        return value

測驗

from booktest.serializers import BookInfoSerializer
data = https://www.cnblogs.com/LYPZX/p/{'btitle': 'python'}
serializer = BookInfoSerializer(data=https://www.cnblogs.com/LYPZX/p/data)
serializer.is_valid()  # False   
serializer.errors
#  {'btitle': [ErrorDetail(string='圖書不是關于Django的', code='invalid')]}

2)validate

在序列化器中需要同時對多個欄位進行比較驗證時,可以定義validate方法來驗證,如

class BookInfoSerializer(serializers.Serializer):
    """圖書資料序列化器"""
    ...

    def validate(self, attrs):
        bread = attrs['bread']
        bcomment = attrs['bcomment']
        if bread < bcomment:
            raise serializers.ValidationError('閱讀量小于評論量')
        return attrs

測驗

from booktest.serializers import BookInfoSerializer
data = https://www.cnblogs.com/LYPZX/p/{'btitle': 'about django', 'bread': 10, 'bcomment': 20}
s = BookInfoSerializer(data=https://www.cnblogs.com/LYPZX/p/data)
s.is_valid()  # False
s.errors
#  {'non_field_errors': [ErrorDetail(string='閱讀量小于評論量', code='invalid')]}

3)validators

在欄位中添加validators選項引數,也可以補充驗證行為,如

def about_django(value):
    if 'django' not in value.lower():
        raise serializers.ValidationError("圖書不是關于Django的")

class BookInfoSerializer(serializers.Serializer):
    """圖書資料序列化器"""
    id = serializers.IntegerField(label='ID', read_only=True)
    btitle = serializers.CharField(label='名稱', max_length=20, validators=[about_django])
    bpub_date = serializers.DateField(label='發布日期', required=False)
    bread = serializers.IntegerField(label='閱讀量', required=False)
    bcomment = serializers.IntegerField(label='評論量', required=False)
    image = serializers.ImageField(label='圖片', required=False)

測驗:

from booktest.serializers import BookInfoSerializer
data = https://www.cnblogs.com/LYPZX/p/{'btitle': 'python'}
serializer = BookInfoSerializer(data=https://www.cnblogs.com/LYPZX/p/data)
serializer.is_valid()  # False   
serializer.errors
#  {'btitle': [ErrorDetail(string='圖書不是關于Django的', code='invalid')]}

2. 保存

如果在驗證成功后,想要基于validated_data完成資料物件的創建,可以通過實作create()和update()兩個方法來實作,

class BookInfoSerializer(serializers.Serializer):
    """圖書資料序列化器"""
    ...

    def create(self, validated_data):
        """新建"""
        return BookInfo(**validated_data)

    def update(self, instance, validated_data):
        """更新,instance為要更新的物件實體"""
        instance.btitle = validated_data.get('btitle', instance.btitle)
        instance.bpub_date = validated_data.get('bpub_date', instance.bpub_date)
        instance.bread = validated_data.get('bread', instance.bread)
        instance.bcomment = validated_data.get('bcomment', instance.bcomment)
        return instance

如果需要在回傳資料物件的時候,也將資料保存到資料庫中,則可以進行如下修改

class BookInfoSerializer(serializers.Serializer):
    """圖書資料序列化器"""
    ...

    def create(self, validated_data):
        """新建"""
        return BookInfo.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """更新,instance為要更新的物件實體"""
        instance.btitle = validated_data.get('btitle', instance.btitle)
        instance.bpub_date = validated_data.get('bpub_date', instance.bpub_date)
        instance.bread = validated_data.get('bread', instance.bread)
        instance.bcomment = validated_data.get('bcomment', instance.bcomment)
        instance.save()
        return instance

實作了上述兩個方法后,在反序列化資料的時候,就可以通過save()方法回傳一個資料物件實體了

book = serializer.save()

如果創建序列化器物件的時候,沒有傳遞instance實體,則呼叫save()方法的時候,create()被呼叫,相反,如果傳遞了instance實體,則呼叫save()方法的時候,update()被呼叫,

from db.serializers import BookInfoSerializer
data = https://www.cnblogs.com/LYPZX/p/{'btitle': '封神演義'}
serializer = BookInfoSerializer(data=data)
serializer.is_valid()  # True
serializer.save()  # <BookInfo: 封神演義>

from db.models import BookInfo
book = BookInfo.objects.get(id=2)
data = https://www.cnblogs.com/LYPZX/p/{'btitle': '倚天劍'}
serializer = BookInfoSerializer(book, data=https://www.cnblogs.com/LYPZX/p/data)
serializer.is_valid()  # True
serializer.save()  # 
book.btitle  #'倚天劍'

說明:

1) 在對序列化器進行save()保存時,可以額外傳遞資料,這些資料可以在create()和update()中的validated_data引數獲取到

serializer.save(owner=request.user)

3.4 模型類序列化器ModelSerializer

如果我們想要使用序列化器對應的是Django的模型類,DRF為我們提供了ModelSerializer模型類序列化器來幫助我們快速創建一個Serializer類,

ModelSerializer與常規的Serializer相同,但提供了:

  • 基于模型類自動生成一系列欄位
  • 基于模型類自動為Serializer生成validators,比如unique_together
  • 包含默認的create()和update()的實作

1. 定義

比如我們創建一個BookInfoSerializer:

class BookInfoSerializer(serializers.ModelSerializer):
    """圖書資料序列化器"""
    class Meta:
        model = BookInfo
        fields = '__all__'
  • model 指明參照哪個模型類
  • fields 指明為模型類的哪些欄位生成

我們可以在python manage.py shell中查看自動生成的BookInfoSerializer的具體實作

>>> from booktest.serializers import BookInfoSerializer
>>> serializer = BookInfoSerializer()
>>> serializer
BookInfoSerializer():
    id = IntegerField(label='ID', read_only=True)
    btitle = CharField(label='名稱', max_length=20)
    bpub_date = DateField(allow_null=True, label='發布日期', required=False)
    bread = IntegerField(label='閱讀量', max_value=https://www.cnblogs.com/LYPZX/p/2147483647, min_value=-2147483648, required=False)
    bcomment = IntegerField(label='評論量', max_value=https://www.cnblogs.com/LYPZX/p/2147483647, min_value=-2147483648, required=False)
    image = ImageField(allow_null=True, label='圖片', max_length=100, required=False)

2. 指定欄位

'''
model=對應的模型
fields=('參與序列化和反序列的欄位1','參與序列化和反序列的欄位2')
fields='__all__'    包含所有欄位
exclude = ('id', 'is_delete')    刨除某些欄位
read_only_fields    只能讀,不能寫
跨表自動深度3種方式(展示外鍵表的所有欄位)
    depth = 1         最大10
                                                # required = False  不是必填傳值
    category_txt = serializers.CharField(source='category.name', required=False)

    x1 = serializers.SerializerMethodField()
    def get_x1(self, obj):
        return obj.category.name

顯示:洗掉和發布文字兩種方式 (1, '發布'),(2, '洗掉')
serializers.CharField(source='get_status_display', required=False)
'''
  1. 使用fields來明確欄位,__all__表名包含所有欄位,也可以寫明具體哪些欄位,如
class BookInfoSerializer(serializers.ModelSerializer):
    """圖書資料序列化器"""
    class Meta:
        model = BookInfo
        fields = ('id', 'btitle', 'bpub_date')
  1. 使用exclude可以明確排除掉哪些欄位
class BookInfoSerializer(serializers.ModelSerializer):
    """圖書資料序列化器"""
    class Meta:
        model = BookInfo
        exclude = ('image',)
  1. 顯示指明欄位,如:
class HeroInfoSerializer(serializers.ModelSerializer):
    hbook = BookInfoSerializer()

    class Meta:
        model = HeroInfo
        fields = ('id', 'hname', 'hgender', 'hcomment', 'hbook')
  1. 指明只讀欄位

可以通過read_only_fields指明只讀欄位,即僅用于序列化輸出的欄位

class BookInfoSerializer(serializers.ModelSerializer):
    """圖書資料序列化器"""
    class Meta:
        model = BookInfo
        fields = ('id', 'btitle', 'bpub_date', 'bread', 'bcomment')
        read_only_fields = ('id', 'bread', 'bcomment')

3. 添加額外引數

我們可以使用extra_kwargs引數為ModelSerializer添加或修改原有的選項引數

class BookInfoSerializer(serializers.ModelSerializer):
    """圖書資料序列化器"""
    class Meta:
        model = BookInfo
        fields = ('id', 'btitle', 'bpub_date', 'bread', 'bcomment')
        extra_kwargs = {
            'bread': {'min_value': 0, 'required': True},
            'bcomment': {'min_value': 0, 'required': True},
        }

# BookInfoSerializer():
#    id = IntegerField(label='ID', read_only=True)
#    btitle = CharField(label='名稱', max_length=20)
#    bpub_date = DateField(allow_null=True, label='發布日期', required=False)
#    bread = IntegerField(label='閱讀量', max_value=https://www.cnblogs.com/LYPZX/p/2147483647, min_value=0, required=True)
#    bcomment = IntegerField(label='評論量', max_value=https://www.cnblogs.com/LYPZX/p/2147483647, min_value=0, required=True)

4. 視圖

4.1 Request 與 Response

1. Request

REST framework 傳入視圖的request物件不再是Django默認的HttpRequest物件,而是REST framework提供的擴展了HttpRequest類的Request類的物件,

REST framework 提供了Parser決議器,在接收到請求后會自動根據Content-Type指明的請求資料型別(如JSON、表單等)將請求資料進行parse決議,決議為類字典物件保存到Request物件中,

Request物件的資料是自動根據前端發送資料的格式進行決議之后的結果,

無論前端發送的哪種格式的資料,我們都可以以統一的方式讀取資料,

常用屬性

1).data

request.data 回傳決議之后的請求體資料,類似于Django中標準的request.POSTrequest.FILES屬性,但提供如下特性:

  • 包含了決議之后的檔案和非檔案資料
  • 包含了對POST、PUT、PATCH請求方式決議后的資料
  • 利用了REST framework的parsers決議器,不僅支持表單型別資料,也支持JSON資料

2).query_params

request.query_params與Django標準的request.GET相同,只是更換了更正確的名稱而已,

2. Response

rest_framework.response.Response

REST framework提供了一個回應類Response,使用該類構造回應物件時,回應的具體資料內容會被轉換(render渲染)成符合前端需求的型別,

構造方式

Response(data, status=None, template_name=None, headers=None, content_type=None)

data資料不要是render處理之后的資料,只需傳遞python的內建型別資料即可,REST framework會使用renderer渲染器處理data

data不能是復雜結構的資料,如Django的模型類物件,對于這樣的資料我們可以使用Serializer序列化器序列化處理后(轉為了Python字典型別)再傳遞給data引數,

引數說明:

  • data: 為回應準備的序列化處理后的資料;
  • status: 狀態碼,默認200;
  • template_name: 模板名稱,如果使用HTMLRenderer 時需指明;
  • headers: 用于存放回應頭資訊的字典;
  • content_type: 回應資料的Content-Type,通常此引數無需傳遞,REST framework會根據前端所需型別資料來設定該引數,

3. 狀態碼

為了方便設定狀態碼,REST framewrok在rest_framework.status模塊中提供了常用狀態碼常量,

1)資訊告知 - 1xx
HTTP_100_CONTINUE
HTTP_101_SWITCHING_PROTOCOLS
2)成功 - 2xx
HTTP_200_OK
HTTP_201_CREATED
HTTP_202_ACCEPTED
HTTP_203_NON_AUTHORITATIVE_INFORMATION
HTTP_204_NO_CONTENT
HTTP_205_RESET_CONTENT
HTTP_206_PARTIAL_CONTENT
HTTP_207_MULTI_STATUS
3)重定向 - 3xx
HTTP_300_MULTIPLE_CHOICES
HTTP_301_MOVED_PERMANENTLY
HTTP_302_FOUND
HTTP_303_SEE_OTHER
HTTP_304_NOT_MODIFIED
HTTP_305_USE_PROXY
HTTP_306_RESERVED
HTTP_307_TEMPORARY_REDIRECT
4)客戶端錯誤 - 4xx
HTTP_400_BAD_REQUEST
HTTP_401_UNAUTHORIZED
HTTP_402_PAYMENT_REQUIRED
HTTP_403_FORBIDDEN
HTTP_404_NOT_FOUND
HTTP_405_METHOD_NOT_ALLOWED
HTTP_406_NOT_ACCEPTABLE
HTTP_407_PROXY_AUTHENTICATION_REQUIRED
HTTP_408_REQUEST_TIMEOUT
HTTP_409_CONFLICT
HTTP_410_GONE
HTTP_411_LENGTH_REQUIRED
HTTP_412_PRECONDITION_FAILED
HTTP_413_REQUEST_ENTITY_TOO_LARGE
HTTP_414_REQUEST_URI_TOO_LONG
HTTP_415_UNSUPPORTED_MEDIA_TYPE
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE
HTTP_417_EXPECTATION_FAILED
HTTP_422_UNPROCESSABLE_ENTITY
HTTP_423_LOCKED
HTTP_424_FAILED_DEPENDENCY
HTTP_428_PRECONDITION_REQUIRED
HTTP_429_TOO_MANY_REQUESTS
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE
HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS
5)服務器錯誤 - 5xx
HTTP_500_INTERNAL_SERVER_ERROR
HTTP_501_NOT_IMPLEMENTED
HTTP_502_BAD_GATEWAY
HTTP_503_SERVICE_UNAVAILABLE
HTTP_504_GATEWAY_TIMEOUT
HTTP_505_HTTP_VERSION_NOT_SUPPORTED
HTTP_507_INSUFFICIENT_STORAGE
HTTP_511_NETWORK_AUTHENTICATION_REQUIRED

4.2 視圖概覽

REST framework 提供了眾多的通用視圖基類與擴展類,以簡化視圖的撰寫,

視圖的繼承關系:

通用視圖繼承關系

視圖的方法與屬性:

視圖的方法與屬性

4.3 視圖說明

1. 兩個基類

1)APIView

rest_framework.views.APIView

APIView是REST framework提供的所有視圖的基類,繼承自Django的View父類,

APIViewView的不同之處在于:

  • 傳入到視圖方法中的是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'^books/$', views.BookListView.as_view()),
class BookListView(APIView):
    def get(self, request):
        books = BookInfo.objects.all()
        serializer = BookInfoSerializer(books, many=True)
        return Response(serializer.data)

2)GenericAPIView

rest_framework.generics.GenericAPIView

繼承自APIVIew,增加了對于串列視圖和詳情視圖可能用到的通用支持方法,通常使用時,可搭配一個或多個Mixin擴展類,

支持定義的屬性:
  • 串列視圖與詳情視圖通用:
    • queryset 串列視圖的查詢集
    • serializer_class 視圖使用的序列化器
  • 串列視圖使用:
    • pagination_class 分頁控制類
    • filter_backends 過濾控制后端
  • 詳情頁視圖使用:
    • lookup_field 查詢單一資料庫物件時使用的條件欄位,默認為'pk'
    • lookup_url_kwarg 查詢單一資料時URL中的引數關鍵字名稱,默認與look_field相同
提供的方法:
  • 串列視圖與詳情視圖通用:

    • get_queryset(self)

      回傳視圖使用的查詢集,是串列視圖與詳情視圖獲取資料的基礎,默認回傳queryset屬性,可以重寫,例如:

      def get_queryset(self):
          user = self.request.user
          return user.accounts.all()
      
    • get_serializer_class(self)

      回傳序列化器類,默認回傳serializer_class,可以重寫,例如:

      def get_serializer_class(self):
          if self.request.user.is_staff:
              return FullAccountSerializer
          return BasicAccountSerializer
      
    • get_serializer(self, *args, **kwargs)

      回傳序列化器物件,被其他視圖或擴展類使用,如果我們在視圖中想要獲取序列化器物件,可以直接呼叫此方法,

      注意,在提供序列化器物件的時候,REST framework會向物件的context屬性補充三個資料:request、format、view,這三個資料物件可以在定義序列化器時使用,

  • 詳情視圖使用:

    • get_object(self) 回傳詳情視圖所需的模型類資料物件,默認使用lookup_field引數來過濾queryset, 在試圖中可以呼叫該方法獲取詳情資訊的模型類物件,

      若詳情訪問的模型類物件不存在,會回傳404,

      該方法會默認使用APIView提供的check_object_permissions方法檢查當前物件是否有權限被訪問,

舉例:

# url(r'^books/(?P<pk>\d+)/$', views.BookDetailView.as_view()),
class BookDetailView(GenericAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer

    def get(self, request, pk):
        book = self.get_object()
        serializer = self.get_serializer(book)
        return Response(serializer.data)

2. 五個擴展類

1)ListModelMixin

串列視圖擴展類,提供list(request, *args, **kwargs)方法快速實作串列視圖,回傳200狀態碼,

該Mixin的list方法會對資料進行過濾和分頁,

源代碼:

class ListModelMixin(object):
    """
    List a queryset.
    """
    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)

舉例:

from rest_framework.mixins import ListModelMixin

class BookListView(ListModelMixin, GenericAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer

    def get(self, request):
        return self.list(request)

2)CreateModelMixin

創建視圖擴展類,提供create(request, *args, **kwargs)方法快速實作創建資源的視圖,成功回傳201狀態碼,

如果序列化器對前端發送的資料驗證失敗,回傳400錯誤,

源代碼:

class CreateModelMixin(object):
    """
    Create a model instance.
    """
    def create(self, request, *args, **kwargs):
        # 獲取序列化器
        serializer = self.get_serializer(data=https://www.cnblogs.com/LYPZX/p/request.data)
        # 驗證
        serializer.is_valid(raise_exception=True)
        # 保存
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
        serializer.save()

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}

3) RetrieveModelMixin

詳情視圖擴展類,提供retrieve(request, *args, **kwargs)方法,可以快速實作回傳一個存在的資料物件,

如果存在,回傳200, 否則回傳404,

源代碼:

class RetrieveModelMixin(object):
    """
    Retrieve a model instance.
    """
    def retrieve(self, request, *args, **kwargs):
        # 獲取物件,會檢查物件的權限
        instance = self.get_object()
        # 序列化
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

舉例:

class BookDetailView(RetrieveModelMixin, GenericAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer

    def get(self, request, pk):
        return self.retrieve(request)

4)UpdateModelMixin

更新視圖擴展類,提供update(request, *args, **kwargs)方法,可以快速實作更新一個存在的資料物件,

同時也提供partial_update(request, *args, **kwargs)方法,可以實作區域更新,

成功回傳200,序列化器校驗資料失敗時,回傳400錯誤,

源代碼:

class UpdateModelMixin(object):
    """
    Update a model instance.
    """
    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=https://www.cnblogs.com/LYPZX/p/request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance,'_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return Response(serializer.data)

    def perform_update(self, serializer):
        serializer.save()

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)

5)DestroyModelMixin

洗掉視圖擴展類,提供destroy(request, *args, **kwargs)方法,可以快速實作洗掉一個存在的資料物件,

成功回傳204,不存在回傳404,

源代碼:

class DestroyModelMixin(object):
    """
    Destroy a model instance.
    """
    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

    def perform_destroy(self, instance):
        instance.delete()

3. 幾個可用子類視圖

1) CreateAPIView

提供 post 方法

繼承自: GenericAPIView、CreateModelMixin

2)ListAPIView

提供 get 方法

繼承自:GenericAPIView、ListModelMixin

3)RetireveAPIView

提供 get 方法

繼承自: GenericAPIView、RetrieveModelMixin

4)DestoryAPIView

提供 delete 方法

繼承自:GenericAPIView、DestoryModelMixin

5)UpdateAPIView

提供 put 和 patch 方法

繼承自:GenericAPIView、UpdateModelMixin

6)RetrieveUpdateAPIView

提供 get、put、patch方法

繼承自: GenericAPIView、RetrieveModelMixin、UpdateModelMixin

7)RetrieveUpdateDestoryAPIView

提供 get、put、patch、delete方法

繼承自:GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestoryModelMixin

4.4 視圖集ViewSet

使用視圖集ViewSet,可以將一系列邏輯相關的動作放到一個類中:

  • list() 提供一組資料
  • retrieve() 提供單個資料
  • create() 創建資料
  • update() 保存資料
  • destory() 洗掉資料

ViewSet視圖集類不再實作get()、post()等方法,而是實作動作 action 如 list() 、create() 等,

視圖集只在使用as_view()方法的時候,才會將action動作與具體請求方式對應上,如:

class BookInfoViewSet(viewsets.ViewSet):

    def list(self, request):
        ...

    def retrieve(self, request, pk=None):
        ...

在設定路由時,我們可以如下操作

urlpatterns = [
    url(r'^books/$', BookInfoViewSet.as_view({'get':'list'}),
    url(r'^books/(?P<pk>\d+)/$', BookInfoViewSet.as_view({'get': 'retrieve'})
]

1. action屬性

在視圖集中,我們可以通過action物件屬性來獲取當前請求視圖集時的action動作是哪個,

例如:

def get_serializer_class(self):
    if self.action == 'create':
        return OrderCommitSerializer
    else:
        return OrderDataSerializer

2. 常用視圖集父類

1) ViewSet

繼承自APIView,作用也與APIView基本類似,提供了身份認證、權限校驗、流量管理等,

在ViewSet中,沒有提供任何動作action方法,需要我們自己實作action方法,

2)GenericViewSet

繼承自GenericAPIView,作用也與GenericAPIVIew類似,提供了get_object、get_queryset等方法便于串列視圖與詳情資訊視圖的開發,

3)ModelViewSet

繼承自GenericAPIVIew,同時包括了ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin,

4)ReadOnlyModelViewSet

繼承自GenericAPIVIew,同時包括了ListModelMixin、RetrieveModelMixin,

3. 視圖集中定義附加action動作

在視圖集中,除了上述默認的方法動作外,還可以添加自定義動作,

添加自定義動作需要使用rest_framework.decorators.action裝飾器,

以action裝飾器裝飾的方法名會作為action動作名,與list、retrieve等同,

action裝飾器可以接收兩個引數:

  • methods: 該action支持的請求方式,串列傳遞

  • detail
    表示是action中要處理的是否是視圖資源的物件(即是否通過url路徑獲取主鍵)
    • True 表示使用通過URL獲取的主鍵對應的資料物件
    • False 表示不使用URL獲取主鍵

舉例:

from rest_framework import mixins
from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action

class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer

    # detail為False 表示不需要處理具體的BookInfo物件
    @action(methods=['get'], detail=False)
    def latest(self, request):
        """
        回傳最新的圖書資訊
        """
        book = BookInfo.objects.latest('id')
        serializer = self.get_serializer(book)
        return Response(serializer.data)

    # detail為True,表示要處理具體與pk主鍵對應的BookInfo物件
    @action(methods=['put'], detail=True)
    def read(self, request, pk):
        """
        修改圖書的閱讀量資料
        """
        book = self.get_object()
        book.bread = request.data.get('read')
        book.save()
        serializer = self.get_serializer(book)
        return Response(serializer.data)

url的定義

urlpatterns = [
    url(r'^books/$', views.BookInfoViewSet.as_view({'get': 'list'})),
    url(r'^books/latest/$', views.BookInfoViewSet.as_view({'get': 'latest'})),
    url(r'^books/(?P<pk>\d+)/$', views.BookInfoViewSet.as_view({'get': 'retrieve'})),
    url(r'^books/(?P<pk>\d+)/read/$', views.BookInfoViewSet.as_view({'put': 'read'})),
]

4. 視圖集的繼承關系

視圖集的繼承關系

4.5 路由Routers

對于視圖集ViewSet,我們除了可以自己手動指明請求方式與動作action之間的對應關系外,還可以使用Routers來幫助我們快速實作路由資訊,

REST framework提供了兩個router

  • SimpleRouter
  • DefaultRouter

1. 使用方法

1) 創建router物件,并注冊視圖集,例如

from rest_framework import routers

router = routers.SimpleRouter()
router.register(r'books', BookInfoViewSet, base_name='book')

register(prefix, viewset, base_name)

  • prefix 該視圖集的路由前綴
  • viewset 視圖集
  • base_name 路由名稱的前綴

如上述代碼會形成的路由如下:

^books/$    name: book-list
^books/{pk}/$   name: book-detail

2)添加路由資料

可以有兩種方式:

urlpatterns = [
    ...
]
urlpatterns += router.urls

urlpatterns = [
    ...
    url(r'^', include(router.urls))
]

2. 視圖集中包含附加action的

class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer

    @action(methods=['get'], detail=False)
    def latest(self, request):
        ...

    @action(methods=['put'], detail=True)
    def read(self, request, pk):
        ...

此視圖集會形成的路由:

^books/latest/$    name: book-latest
^books/{pk}/read/$  name: book-read

3. 路由router形成URL的方式

1) SimpleRouter

SimpleRouter

2)DefaultRouter

DefaultRouter

DefaultRouter與SimpleRouter的區別是,DefaultRouter會多附帶一個默認的API根視圖,回傳一個包含所有串列視圖的超鏈接回應資料,

5. 其他功能

5.1 版本

可以在組態檔中配置全域默認的版本方案

REST_FRAMEWORK = {
    # 版本配置
    'DEFAULT_VERSIONING_CLASS': "rest_framework.versioning.URLPathVersioning",
    "ALLOWED_VERSIONS": ['v1', 'v2'],
}

urls

from django.conf.urls import url, include
urlpatterns = [
    # 版本管理
    url(r'^api/(?P<version>\w+)/', include('api.urls')),
]

5.2 認證Authentication

可以在組態檔中配置全域默認的認證方案

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',   # 基本認證
        'rest_framework.authentication.SessionAuthentication',  # session認證
    )
}

也可以在每個視圖中通過設定authentication_classess屬性來設定

from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.views import APIView

class ExampleView(APIView):
    authentication_classes = (SessionAuthentication, BasicAuthentication)
    ...

認證失敗會有兩種可能的回傳值:

  • 401 Unauthorized 未認證
  • 403 Permission Denied 權限被禁止

5.3 權限Permissions

權限控制可以限制用戶對于視圖的訪問和對于具體資料物件的訪問,

  • 在執行視圖的dispatch()方法前,會先進行視圖訪問權限的判斷
  • 在通過get_object()獲取具體物件時,會進行物件訪問權限的判斷

使用

可以在組態檔中設定默認的權限管理類,如

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    )
}

如果未指明,則采用如下默認配置

'DEFAULT_PERMISSION_CLASSES': (
   'rest_framework.permissions.AllowAny',
)

也可以在具體的視圖中通過permission_classes屬性來設定,如

from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView

class ExampleView(APIView):
    permission_classes = (IsAuthenticated,)
    ...

提供的權限

  • AllowAny 允許所有用戶
  • IsAuthenticated 僅通過認證的用戶
  • IsAdminUser 僅管理員用戶
  • IsAuthenticatedOrReadOnly 認證的用戶可以完全操作,否則只能get讀取

舉例

from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.generics import RetrieveAPIView

class BookDetailView(RetrieveAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    authentication_classes = [SessionAuthentication]
    permission_classes = [IsAuthenticated]

5.4 限流Throttling

可以對介面訪問的頻次進行限制,以減輕服務器壓力,

如何實作的評率限制

- 匿名用戶,用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.可以訪問

使用

可以在組態檔中,使用DEFAULT_THROTTLE_CLASSESDEFAULT_THROTTLE_RATES進行全域配置,

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle'
    ),
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day',
        'user': '1000/day'
    }
}

DEFAULT_THROTTLE_RATES 可以使用 second, minute, hourday來指明周期,

也可以在具體視圖中通過throttle_classess屬性來配置,如

from rest_framework.throttling import UserRateThrottle
from rest_framework.views import APIView

class ExampleView(APIView):
    throttle_classes = (UserRateThrottle,)
    ...

可選限流類

1) AnonRateThrottle

限制所有匿名未認證用戶,使用IP區分用戶,

使用DEFAULT_THROTTLE_RATES['anon'] 來設定頻次

2)UserRateThrottle

限制認證用戶,使用User id 來區分,

使用DEFAULT_THROTTLE_RATES['user'] 來設定頻次

3)ScopedRateThrottle

限制用戶對于每個視圖的訪問頻次,使用ip或user id,

例如:

class ContactListView(APIView):
    throttle_scope = 'contacts'
    ...

class ContactDetailView(APIView):
    throttle_scope = 'contacts'
    ...

class UploadView(APIView):
    throttle_scope = 'uploads'
    ...
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.ScopedRateThrottle',
    ),
    'DEFAULT_THROTTLE_RATES': {
        'contacts': '1000/day',
        'uploads': '20/day'
    }
}

實體

from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.generics import RetrieveAPIView
from rest_framework.throttling import UserRateThrottle

class BookDetailView(RetrieveAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    authentication_classes = [SessionAuthentication]
    permission_classes = [IsAuthenticated]
    throttle_classes = (UserRateThrottle,)

5.5 過濾Filtering

對于串列資料可能需要根據欄位進行過濾,我們可以通過添加django-fitlter擴展來增強支持,

pip insall django-filter

在組態檔中增加過濾后端的設定:

INSTALLED_APPS = [
    ...
    'django_filters',  # 需要注冊應用,
]

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}

在視圖中添加filter_fields屬性,指定可以過濾的欄位

class BookListView(ListAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    filter_fields = ('btitle', 'bread')

# 127.0.0.1:8000/books/?btitle=西游記

5.6 排序

對于串列資料,REST framework提供了OrderingFilter過濾器來幫助我們快速指明資料按照指定欄位進行排序,

使用方法:

在類視圖中設定filter_backends,使用rest_framework.filters.OrderingFilter過濾器,REST framework會在請求的查詢字串引數中檢查是否包含了ordering引數,如果包含了ordering引數,則按照ordering引數指明的排序欄位對資料集進行排序,

前端可以傳遞的ordering引數的可選欄位值需要在ordering_fields中指明,

示例:

class BookListView(ListAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    filter_backends = [OrderingFilter]
    ordering_fields = ('id', 'bread', 'bpub_date')

# 127.0.0.1:8000/books/?ordering=-bread

5.7 分頁Pagination

REST framework提供了分頁的支持,

我們可以在組態檔中設定全域的分頁方式,如:

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS':  'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 100  # 每頁數目
}

也可通過自定義Pagination類,來為視圖添加不同分頁行為,在視圖中通過pagination_clas屬性來指明,

class LargeResultsSetPagination(PageNumberPagination):
    page_size = 1000
    page_size_query_param = 'page_size'
    max_page_size = 10000
class BookDetailView(RetrieveAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    pagination_class = LargeResultsSetPagination

注意:如果在視圖內關閉分頁功能,只需在視圖內設定

pagination_class = None

可選分頁器

1) PageNumberPagination

前端訪問網址形式:

GET  http://api.example.org/books/?page=4

可以在子類中定義的屬性:

  • page_size 每頁數目
  • page_query_param 前端發送的頁數關鍵字名,默認為"page"
  • page_size_query_param 前端發送的每頁數目關鍵字名,默認為None
  • max_page_size 前端最多能設定的每頁數量

基礎版 APIView

from rest_framework.pagination import PageNumberPagination
from rest_framework import serializers

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

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

優化版 ListAPIView

from rest_framework.pagination import PageNumberPagination

class StandardPageNumberPagination(PageNumberPagination):
    page_size_query_param = 'page_size'
    max_page_size = 10

class BookListView(ListAPIView):
    queryset = BookInfo.objects.all().order_by('id')
    serializer_class = BookInfoSerializer
    pagination_class = StandardPageNumberPagination

# 127.0.0.1/books/?page=1&page_size=2

2)LimitOffsetPagination

前端訪問網址形式:

GET http://api.example.org/books/?limit=100&offset=400

可以在子類中定義的屬性:

  • default_limit 默認限制,默認值與PAGE_SIZE設定一直
  • limit_query_param limit引數名,默認'limit'
  • offset_query_param offset引數名,默認'offset'
  • max_limit 最大limit限制,默認None

APIView 版

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)

ListAPIView版本

from rest_framework.pagination import LimitOffsetPagination

class BookListView(ListAPIView):
    queryset = BookInfo.objects.all().order_by('id')
    serializer_class = BookInfoSerializer
    pagination_class = LimitOffsetPagination

# 127.0.0.1:8000/books/?offset=3&limit=2

5.78 例外處理 Exceptions

REST framework提供了例外處理,我們可以自定義例外處理函式,

from rest_framework.views import exception_handler

def custom_exception_handler(exc, context):
    # 先呼叫REST framework默認的例外處理方法獲得標準錯誤回應物件
    response = exception_handler(exc, context)

    # 在此處補充自定義的例外處理
    if response is not None:
        response.data['status_code'] = response.status_code

    return response

在組態檔中宣告自定義的例外處理

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}

如果未宣告,會采用默認的方式,如下

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
}

例如:

補充上處理關于資料庫的例外

from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework import status
from django.db import DatabaseError

def exception_handler(exc, context):
    response = drf_exception_handler(exc, context)

    if response is None:
        view = context['view']
        if isinstance(exc, DatabaseError):
            print('[%s]: %s' % (view, exc))
            response = Response({'detail': '服務器內部錯誤'}, status=status.HTTP_507_INSUFFICIENT_STORAGE)

    return response

REST framework定義的例外

  • APIException 所有例外的父類
  • ParseError 決議錯誤
  • AuthenticationFailed 認證失敗
  • NotAuthenticated 尚未認證
  • PermissionDenied 權限決絕
  • NotFound 未找到
  • MethodNotAllowed 請求方式不支持
  • NotAcceptable 要獲取的資料格式不支持
  • Throttled 超過限流次數
  • ValidationError 校驗失敗

5.9 自動生成介面檔案

REST framework可以自動幫助我們生成介面檔案,

介面檔案以網頁的方式呈現,

自動介面檔案能生成的是繼承自APIView及其子類的視圖,

1. 安裝依賴

REST framewrok生成介面檔案需要coreapi庫的支持,

pip install coreapi

2. 設定介面檔案訪問路徑

在總路由中添加介面檔案路徑,

檔案路由對應的視圖配置為rest_framework.documentation.include_docs_urls

引數title為介面檔案網站的標題,

from rest_framework.documentation import include_docs_urls

urlpatterns = [
    ...
    url(r'^docs/', include_docs_urls(title='My API title'))
]

3. 檔案描述說明的定義位置

1) 單一方法的視圖,可直接使用類視圖的檔案字串,如

class BookListView(generics.ListAPIView):
    """
    回傳所有圖書資訊.
    """

2)包含多個方法的視圖,在類視圖的檔案字串中,分開方法定義,如

class BookListCreateView(generics.ListCreateAPIView):
    """
    get:
    回傳所有圖書資訊.

    post:
    新建圖書.
    """

3)對于視圖集ViewSet,仍在類視圖的檔案字串中分開定義,但是應使用action名稱區分,如

class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
    """
    list:
    回傳圖書串列資料

    retrieve:
    回傳圖書詳情資料

    latest:
    回傳最新的圖書資料

    read:
    修改圖書的閱讀量
    """

4. 訪問介面檔案網頁

瀏覽器訪問 127.0.0.1:8000/docs/,即可看到自動生成的介面檔案,

介面檔案網頁

兩點說明:

1) 視圖集ViewSet中的retrieve名稱,在介面檔案網站中叫做read

2)引數的Description需要在模型類或序列化器類的欄位中以help_text選項定義,如:

class BookInfo(models.Model):
    ...
    bread = models.IntegerField(default=0, verbose_name='閱讀量', help_text='閱讀量')
    ...

class BookReadSerializer(serializers.ModelSerializer):
    class Meta:
        model = BookInfo
        fields = ('bread', )
        extra_kwargs = {
            'bread': {
                'required': True,
                'help_text': '閱讀量'
            }
        }

5.10 JWT

1 介紹

用于在前后端分離時,實作用戶登錄相關,

一般用戶認證有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.字串合法性校驗,
    

2 安裝

pip3 install djangorestframework-jwt

注冊:在settings.pyINSTALLED_APPS中添加'rest_framework',

INSTALLED_APPS = [
    'rest_framework_jwt'
]

REST_FRAMEWORK = {
    # 全域認證配置
    "DEFAULT_AUTHENTICATION_CLASSES": ["api.extensions.auth.HulaQueryParamAuthentication", ],
    # "UNAUTHENTICATED_USER": None,
    # "UNAUTHENTICATED_TOKEN": None,
}
# token過期時間
import datetime

JWT_AUTH = {
    "JWT_EXPIRATION_DELTA": datetime.timedelta(minutes=10)
}

3 案例

  • 用戶認證

    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)
    
  • 用戶登錄

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework_jwt.settings import api_settings
    from api import models
    
    class LoginView(APIView):
        """登錄介面"""
        authentication_classes = []  # 不校驗認證
    
        def post(self, request, *args, **kwargs):
            """基于Token的認證"""
            # 1.根據用戶名和密碼檢測用戶是否可以登錄
            user = models.UserInfo.objects.filter(username=request.data.get('username'),
                                                  password=request.data.get('password')).first()
            if not user:
                return Response({'code': 10001, 'error': '用戶名或密碼錯誤'})
    
            # 2. 根據user物件生成payload(中間值的資料)
            jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
            payload = jwt_payload_handler(user)
    
            # 3. 構造前面資料,base64加密;中間資料base64加密;前兩段拼接然后做hs256加密(加鹽),再做base64加密,生成token
            jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
            token = jwt_encode_handler(payload)
            return Response({'code': 10000, 'data': token})
    

5.11 跨域

由于瀏覽器具有“同源策略”的限制,
如果在同一個域下發送ajax請求,瀏覽器的同源策略不會阻止,
如果在不同域下發送ajax,瀏覽器的同源策略會阻止,

image-20200810094721027

  • 域相同,永遠不會存在跨域,
    • crm,非前后端分離,沒有跨域,
    • 路飛學城,前后端分離,沒有跨域(之前有,現在沒有),
  • 域不同時,才會存在跨域,
    • l拉勾網,前后端分離,存在跨域(設定回應頭解決跨域)

1 解決跨域:CORS

本質在資料回傳值設定回應頭

from django.shortcuts import render,HttpResponse

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

2 跨域時,發送了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
    
條件:
    1、請求方式:HEAD、GET、POST
    2、請求頭資訊:
        Accept
        Accept-Language
        Content-Language
        Last-Event-ID
        Content-Type 對應的值是以下三個中的任意一個
                                application/x-www-form-urlencoded
                                multipart/form-data
                                text/plain
 
注意:同時滿足以上兩個條件時,則是簡單請求,否則為復雜請求

3 總結

  1. 由于瀏覽器具有“同源策略”的限制,所以在瀏覽器上跨域發送Ajax請求時,會被瀏覽器阻止,
  2. 解決跨域
    • 不跨域
    • CORS(跨站資源共享,本質是設定回應頭來解決),
      • 簡單請求:發送一次請求
      • 復雜請求:發送兩次請求

5.12 paramiko

用于幫助開發者通過代碼遠程連接服務器,并對服務器進行操作,

pip3 install paramiko 
  • 遠程執行命令【用戶名和密碼】

    import paramiko
    
    # 創建SSH物件
    ssh = paramiko.SSHClient()
    
    # 允許連接不在know_hosts檔案中的主機
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    
    # 連接服務器
    ssh.connect(hostname='192.168.16.85', port=22, username='root', password='123456')
    
    # 執行命令
    stdin, stdout, stderr = ssh.exec_command('df')
    # 獲取命令結果
    result = stdout.read()
    # 關閉連接
    ssh.close()
    
    print(result.decode('utf-8'))
    
  • 遠程執行命令【公鑰和私鑰】(公鑰必須提前上傳到服務器)

    import paramiko
    
    private_key = paramiko.RSAKey.from_private_key_file(r'C:/Users/Administrator/.ssh/id_rsa')
    
    # 創建SSH物件
    ssh = paramiko.SSHClient()
    # 允許連接不在know_hosts檔案中的主機
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    # 連接服務器
    ssh.connect(hostname='192.168.16.85', port=22, username='root', pkey=private_key)
    
    # 執行命令
    stdin, stdout, stderr = ssh.exec_command('df')
    # 獲取命令結果
    result = stdout.read()
    
    # 關閉連接
    ssh.close()
    
    print(result)
    
  • 遠程上傳和下載檔案【用戶名和密碼】

    import paramiko
    
    transport = paramiko.Transport(('192.168.16.85', 22))
    transport.connect(username='root', password='123456')
    sftp = paramiko.SFTPClient.from_transport(transport)
    
    
    # 將location.py 上傳至服務器 /tmp/test.py
    # sftp.put('wy.txt', '/data/wy.txt')
    sftp.get('/data/wy.txt', 'xx.txt')
    
    transport.close()
    
  • 遠程上傳和下載檔案【公鑰和私鑰】

    import paramiko
    
    private_key = paramiko.RSAKey.from_private_key_file(r'C:/Users/Administrator/.ssh/id_rsa')
    
    transport = paramiko.Transport(('192.168.16.85', 22))
    transport.connect(username='root', pkey=private_key)
    
    sftp = paramiko.SFTPClient.from_transport(transport)
    # 將location.py 上傳至服務器 /tmp/test.py
    # sftp.put('/tmp/location.py', '/tmp/test.py')
    
    # 將remove_path 下載到本地 local_path
    # sftp.get('remove_path', 'local_path')
    
    transport.close()
    

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/50456.html

標籤:Python

上一篇:hook框架frida 繞過fillder證書認證

下一篇:Python append() 串列方法

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more