1. 程式人生 > >DRF 框架學習小結

DRF 框架學習小結

前言:

django restful framework框架,繼續學習中。發現了一篇不錯的文章。


1、 RESTful是一種API的命名風格。
2、 前後端分離: 使用者訪問靜態檔案的伺服器,資料全部由ajax請求給到。
3、 RESTful風格:資料應該是名詞,而動詞由HTTP的請求方式來體現。
4、 RESTful風格的API給前端返回結果物件,無論什麼請求方式。

'''
特點: 反覆重複
因為不論什麼請求方式,都需要給前端返回物件內容,就是json格式的
所以每次如果有查詢的結果物件都需要遍歷成字典,和flask相同

如果不是get請求是帶有內容的請求,那從前端接收的是json格式
每次都需要從request.body中拿出內容,是bytes格式
然後decode解碼成json字串然後再loads成可以給python處理的字典
'''

'''
說明:
return JsonResponse(book_list, safe=False)
# 對safe的說明,我們傳過去的book_list是一個list格式
# 在前端json支援{}格式也支援[]格式
# 但是django中認為[]的json格式是不安全的會進行校驗
# 所以把safe選項關閉False,不進行校驗就可以傳[]
'''

'''
序列化:對查詢結果進行遍歷,然後轉成字典,給到JsonResponse
反序列化:接收前端json處理成字典,然後校驗
'''

'''

正文:


1.DRF框架工程搭建,建立在django的基礎上
安裝DRF: pip install djangorestframework
註冊DRF: INSTALLED_APPS = ['rest_framework',]
在子應用中serializers.py建立序列化器,用於執行序列化和反序列化
在views中類檢視使用序列化器,在urls中寫地址
'''

'''
2.序列化器: serializer

定義: 其實ModelSerializer是Serializer的子類,更方便有模型類的序列化器的建立,實際產生的序列化器如下
class BookInfoSerializer(serializers.Serializer):   實際繼承Serializer
    """圖書資料序列化器"""      序列化器:執行序列化和反序列化
    id = serializers.IntegerField(label='ID', read_only=True)     read_only:只在輸出響應中使用,就是給前端的時候,而前端給我們傳請求的時候,不做驗證
    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(instance=None, data=empty, **kwarg)
             說明: 序列化時,將模型類物件傳入instance引數   instance = 序列化物件
                    反序列化時,將要被反序列化的資料傳入data引數    data = 反序列化物件
                    可通過context引數額外新增資料 即 **kwarg : context={'request': request}  通過Serializer物件的context屬性獲取
'''

'''
3.序列化操作 : 其實就是查詢到物件之後,遍歷構造字典的過程,而JsonResponse由內建的 Renderer渲染器來執行

3-1.序列化只使用序列化器物件的第一個引數instance
serializer = BookInfoSerializer(instance = book)
通過data屬性可以獲取序列化後的資料,這個data跟第二個引數可不是一個
serializer.data
# {'id': 2, 'btitle': '天龍八部', 'bpub_date': '1986-07-24', 'bread': 36, 'bcomment': 40, 'image': None}


3-2.如果要被序列化的是包含多條資料的查詢集QuerySet,新增many=True引數
book_qs = BookInfo.objects.all()
serializer = BookInfoSerializer(book_qs, many=True)
serializer.data
'''

'''
4.關聯物件巢狀序列化(由hero->book通過 hbook方法)

4-1.hbook是個外來鍵: PrimaryKeyRelatedField
hbook = serializers.PrimaryKeyRelatedField(label='圖書', read_only=True)
因為是外來鍵,第二個位置必須有read_only=True 或者 查詢集 queryset=BookInfo.objects.all() 要不報錯
serializer.data 序列化的時候 結果是 關聯物件的主鍵 {'hbook': 2}   即 book.id

4-2.因為id不直觀,想要詳細內容的字串,把外來鍵欄位改為 :  StringRelatedField
hbook = serializers.StringRelatedField(label='圖書')
結果: {'hbook': '天龍八部'}

4-3.介面連結: HyperlinkedRelatedField
hbook = serializers.HyperlinkedRelatedField(label='圖書', read_only=True, view_name='books-detail')
必須指明view_name引數,以便DRF根據檢視名稱尋找路由,進而拼接成完整URL   這個view_name傳什麼:url中有1個引數,是名稱空間,是跟它關聯 
結果: {'hbook': 'http://127.0.0.1:8000/books/2/'}

4-4.關聯物件的指定欄位資料 :  SlugRelatedField
hbook = serializers.SlugRelatedField(label='圖書', read_only=True, slug_field='bpub_date')
slug_field指明使用關聯物件的哪個欄位
結果:{'hbook': datetime.date(1986, 7, 24)}

4-5.使用關聯物件的序列化器:  直接把所屬book的所有內容序列化
hbook = BookInfoSerializer()
結果:{'hbook': OrderedDict([('id', 2), ('btitle', '天龍八部')te', '1986-07-24'), ('bread', 36), ('bcomment', 40), ('image', None)])}

'''
5.重寫to_representation方法
序列化器的每個欄位實際都是由該欄位的to_representation方法決定展示格式的,可以通過重寫該方法來決定格式
注意,to_representations方法不僅侷限在控制關聯物件格式上,適用於各個序列化器欄位型別
class BookRelateField(serializers.RelatedField):
    """自定義新的關聯欄位"""
    def to_representation(self, value):
        return 'Book: %d %s' % (value.id, value.btitle)


hbook = BookRelateField(read_only=True)
結果:{'hbook': 'Book: 2 天龍八部'}

重點: 上邊的都是 多對一 關係 即使用hbook
    如果是 一對多 關係 即使用 heroinfo_set.all  此時關聯欄位型別通用,即上邊的欄位通用,但是需要新增many=True的引數
heroinfo_set = serializers.PrimaryKeyRelatedField(read_only=True, many=True)  

'''

'''
6.反序列化 : 接收前端傳過來的json處理是由 Parser解析器 來執行,反序列化只進行驗證和儲存

使用: 
data = {'bpub_date': 123}
serializer = BookInfoSerializer(data=data)  構造物件,第一個引數instance不傳,傳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  # {}         標題沒傳,時間寫錯,當然獲取不到,驗證都沒通過

6-1.驗證 is_valid()方法
(1)報錯:  序列化器物件的errors屬性獲取錯誤資訊,返回字典,包含了欄位和欄位的錯誤
        非欄位錯誤,可以通過修改REST framework配置中的NON_FIELD_ERRORS_KEY來控制錯誤字典中的鍵名
把報錯給前端,顯示HTTP 400 Bad Request請求錯誤 : is_valid(raise_exception=True) 開啟引數      

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

(3)自定義驗證,在執行is_valid時驗證

(3-1)validate_<field_name> : 對欄位進行驗證
class BookInfoSerializer(serializers.Serializer):
    def validate_btitle(self, value):   驗證btitle欄位,value是傳入的btitle值
        if 'django' not in value.lower():
            raise serializers.ValidationError("圖書不是關於Django的")
        return value

(3-2)validate : 對多個欄位進行比較驗證時
class BookInfoSerializer(serializers.Serializer):
    def validate(self, attrs):      使用attrs
        bread = attrs['bread']
        bcomment = attrs['bcomment']
        if bread < bcomment:
            raise serializers.ValidationError('閱讀量小於評論量')
        return attrs
        
(3-3)validators : 在欄位中新增validators選項引數,也可以補充驗證行為,這時候驗證函式名隨便起,   
                    但是驗證哪個欄位就在哪個欄位加,其實就是方法(1-1),但是函式是寫在class外部的,全域性的
def about_django(value):
    if 'django' not in value.lower():
        raise serializers.ValidationError("圖書不是關於Django的")  注意並不需要return
class BookInfoSerializer(serializers.Serializer):
    btitle = serializers.CharField(label='名稱', max_length=20, validators=[about_django]) 注意是[]
    
REST framework內建的validators :
    單欄位唯一 : UniqueValidator
            validators=[UniqueValidator(queryset=BlogPost.objects.all())]
    聯合唯一 : UniqueTogetherValidation     是都唯一還是有一個唯一就行
            class ExampleSerializer(serializers.Serializer):
                # 寫在類中
                class Meta:
                    validators = [
                        UniqueTogetherValidator(
                            queryset=ToDoItem.objects.all(),
                            fields=('list', 'position')
                        )]
'''

'''
6-2.儲存

驗證成功,validated_data可以取出資料,serializer.save()儲存並返回資料物件,實際是執行create()和update()方法,好像不用重寫,但是講義中有具體執行過程

說明:
serializer.save(),save中可以傳引數,引數可以在create()和update()中的validated_data引數獲取到
預設序列化器必須傳遞所有required的欄位,否則會丟擲驗證異常。但是我們可以使用partial引數來允許部分欄位更新
serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)

針對序列化器:三個引數,第一個引數好像可以作反序列化的物件使用, 有物件且必有data引數
                    而如果是序列化第一個引數作為序列化的物件,遍歷它
'''

'''
7.子類 ModelSerializer

class BookInfoSerializer(serializers.ModelSerializer):
    """圖書資料序列化器"""
    class Meta:
        model = BookInfo        # 依據的模型類
        fields = '__all__'      # 所有欄位

可以驗證(shell): serializer = BookInfoSerializer()
            >>> serializer  可以看到結構

7-1.指定欄位
(1) 指定 fields = ('id', 'btitle', 'bpub_date')
(2) 排除 exclude = ('image',)  注意是元組
(3) 指明只讀欄位 read_only_fields = ('id', 'bread', 'bcomment') 即只在序列化輸出時使用

7-2.巢狀關係欄位  depth
預設生成的hbook = PrimaryKeyRelatedField(label='圖書', queryset=BookInfo.objects.all())就是個外來鍵,並且以id來關聯
巢狀的層級和詳細資訊   depth = 1
    hbook = NestedSerializer(read_only=True): 並且有所屬書籍的詳細資訊

7-3.新增或修改原有引數
extra_kwargs = {
            'bread': {'min_value': 0, 'required': True}},
            'bcomment': {'min_value': 0, 'required': True}},
        }

修改了預設生成的序列化器中欄位的引數
bread = IntegerField(label='閱讀量', max_value=2147483647, min_value=0, required=True)
bcomment = IntegerField(label='評論量', max_value=2147483647, min_value=0, required=True)
'''

後續

'''
1.request和response

DRF的request是Parser解析器處理過的請求
request.data : POST,PUT,PATCH,表單,json
request.query_params : ?後邊的引數

DRF的response是Renderer渲染器處理過的響應
需要在settings中加入配置:
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': (  # 預設響應渲染類
        'rest_framework.renderers.JSONRenderer',  # json渲染器
        'rest_framework.renderers.BrowsableAPIRenderer',  # 瀏覽API渲染器
    )
}

使用:Response(data, status=None, template_name=None, headers=None, content_type=None)  data:字典(序列化後的資料)
常用屬性:
        response.data:傳給response物件的序列化後,但尚未render處理的資料
        response.status_code:狀態碼
        response.context:經過render處理後的響應資料
有個狀態碼常量模組
'''

'''
2.類檢視
提供一堆類檢視APIView子類,就是不斷繼承重新,為了使用特定的方法

2-1.APIView類:(繼承於django的View)
繼承問題: django是繼承View,現在DRF是繼承APIView,Parses直譯器和Renderer渲染器對request和response物件的構建,是APIView類來實現的
特點:
    任何APIException異常都會被捕獲到,並且處理成合適的響應資訊;
    在進行dispatch()分發前,會對請求進行身份認證、許可權檢查、流量控制
    即可以自定義的屬性: authentication_classes 列表或元祖,身份認證類
                     permissoin_classes 列表或元祖,許可權檢查類
                     throttle_classes 列表或元祖,流量控制類

2-2.GenericAPIView類:(繼承於APIView)
增加了對於列表檢視和詳情檢視可能用到的通用支援方法。通常使用時,可搭配一個或多個Mixin擴充套件類。
可以自定義的屬性:
    列表檢視與詳情檢視通用:
        queryset 列表檢視的查詢集
        serializer_class 檢視使用的序列化器
    列表檢視使用:
        pagination_class 分頁控制類
        filter_backends 過濾控制後端
    詳情頁檢視使用:
        lookup_field 查詢單一資料庫物件時使用的條件欄位,預設為'pk'
        lookup_url_kwarg 查詢單一資料時URL中的引數關鍵字名稱,預設與look_field相同
        
可以自定義的方法:
    列表檢視與詳情檢視通用:
        get_queryset(self)  返回檢視使用的查詢集
        get_serializer_class(self)  返回序列化器類
        get_serializer(self, args, *kwargs)  返回序列化器物件
    詳情檢視使用:
        get_object(self)  返回詳情檢視所需的模型類資料物件
        
# 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()    根據pk找到需要的物件
        serializer = self.get_serializer(book)  把書的物件傳到序列化器中
        return Response(serializer.data)

3.擴充套件類:
3-1.ListModelMixin: list方法會對資料進行過濾和分頁
class BookListView(ListModelMixin, GenericAPIView): 和django的Mixin擴充套件類繼承相同
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer

    def get(self, request):
        return self.list(request)
3-2.CreateModelMixin : create方法快速實現建立資源的檢視,成功返回201,資料驗證失敗400錯誤
3-3.RetrieveModelMixin : retrieve方法返回一個存在的資料物件,存在,返回200, 否則返回404
     def get(self, request, pk):
        return self.retrieve(request)
3-4.UpdateModelMixin : update方法更新一個存在的資料物件
                        partial_update區域性更新, 成功返回200,序列化器校驗資料失敗時,返回400錯誤
3-5.DestroyModelMixin : destroy方法刪除一個存在的資料物件,成功返回204,不存在返回404

還有幾個子類檢視,就是把GenericAPIView和Mixin功能寫一起了,看講義
'''

'''
3.檢視集ViewSet

list() 提供一組資料
retrieve() 提供單個數據
create() 建立資料
update() 儲存資料
destory() 刪除資料

action行為:
url(r'^books/$', BookInfoViewSet.as_view('get', 'list')),       get方法獲取所有書籍-> list方法
url(r'^books/(?P<pk>\d+)/$', BookInfoViewSet.as_view('get', 'retrieve'))

常用檢視集父類:
ViewSet(繼承自APIView):提供了身份認證、許可權校驗、流量管理等
GenericViewSet(繼承自GenericAPIView)
ModelViewSet(繼承自GenericAPIVIew,同時包括了所有5個擴充套件)
ReadOnlyModelViewSet(繼承自GenericAPIVIew,同時包括了ListModelMixin、RetrieveModelMixin)

自定義action:
action裝飾器: methods  支援的請求方式,列表傳遞,
            detail  action中要處理的是否是檢視資源的物件(即是否通過url路徑獲取主鍵)\
                    True 表示使用通過URL獲取的主鍵對應的資料物件
                    False 表示不使用URL獲取主鍵

# 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(r'^books/latest/$', views.BookInfoViewSet.as_view({'get': 'latest'})),
url(r'^books/(?P<pk>\d+)/read/$', views.BookInfoViewSet.as_view({'put': 'read'})),
       
'''