1. 程式人生 > >Django REST Framework(DRF)_第一篇

Django REST Framework(DRF)_第一篇

  • 認識RESTful

    • REST是設計風格而不是標準,簡單來講REST規定url是用來唯一定位資源,而http請求方式則用來區分使用者行為.

    • REST介面設計規範

      • HTTP常用動詞

        • GET /books:列出所有書籍 返回資料型別-->[{},{}]

        • GET /books/ID:獲取某個指定書籍的資訊 -->{單本書籍}

        • POST /books:新建一本書籍 -->{新增的資料}

        • PUT /books/ID:更新某本指定書籍的資訊(提供該書籍的全部資訊) -->{修改後的資料}

        • PATCH /books/ID:更新某本指定書籍的資訊(提供該書籍的部分資訊) -->{修改後的資料}

        • DELETE /books/ID:刪除某本書籍 -->返回空

      • 錯誤訊息規範

        • { error: "Invalid API key" }

  • APIView請求流程(原始碼剖析)

    • from rest_framework.views import APIView(匯入APIView)

    • 啟動django後,載入settings及各個元件(檢視,路由等)

    • 載入的時候urls中views.HomeView.as_view(),會執行as_view()類方法,這裡APIView繼承了View的as_view方法,所以最後返回的還是view函式,實際上此時django內部就已經幫我們做了一層url和檢視函式的關係繫結

    • 以訪問首頁為例,找到url對應的檢視函式,執行view(request),返回APIView中的dispatch()並執行該方法

    • 找到對應的get或者post等方法並執行,最終將結果返回給瀏覽器

  • 解析器元件

    • 解析器元件的使用

      • request.data(通過該方法得到處理完的字典資料)

    • 解析器請求流程(原始碼剖析)

      • 簡單的講解析器就是根據不同的content-type來解析資料,最終返回字典格式的資料

      • 在執行view函式時,首次將request傳入,執行APIView中的dispatch()方法

      • 在dispatch()中將原來的request物件傳遞給初始化函式: request = self.initialize_request(request, *args, **kwargs)

      • 執行APIView中的initialize_request,返回Request(request,parsers=self.get_parsers(),..)

      • from rest_framework.request import Request,從這裡可以看出,Request是一個類,所以Request()是一個例項化物件

      • 而我們通過request.data觸發解析方法,此時呼叫Request例項的data方法(這裡用了@property)

      • 在data方法裡面,執行self.load_data_and_files(),此時self指的是Request的例項化物件,所以執行Request中的load_data_and_files()方法

      • load_data_and_files中,執行Request中的self.parse()方法

      • 在self._parse()方法中,首先media_type = self.content_type,獲取了content_type,並執行了parser = self.negotiator.select_parser(self, self.parsers),這裡傳入了self.parsers,而Request例項化的時候傳入了parsers=self.get_parsers(),所以這裡要看下self.get_parsers()該方法

      • 在APIView中執行get_parsers()方法,return [parser() for parser in self.parser_classes],返回了一個列表推導式,而parser_classes = api_settings.DEFAULT_PARSER_CLASSES,那api_settings是啥呢?

      • from rest_framework.settings import api_settings,從這裡我們可以知道是從settings中匯入的,在settings中api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS),APISettings是一個類,所以api_settings是APISettings的例項化物件

      • 例項化物件後,要執行api_settings.DEFAULT_PARSER_CLASSES,但是APISettings類中並沒有該屬性或方法,則執行getattr方法, if attr in self.import_strings: val = perform_import(val, attr),這裡動態匯入,最後返回的是parser_classes 就等於[<class 'rest_framework.parsers.JSONParser'>,<class 'rest_framework.parsers.FormParser'>, <class 'rest_framework.parsers.MultiPartParser'>]

      • parser() for parser in self.parser_classes中parser()是例項化各個類,parsers=self.get_parsers(),parser = self.negotiator.select_parser(self, self.parsers),所以最後這個一執行就觸發前面的一步一步執行

      • 最終self.data, self.files = self.parse(),將self.parse()的返回值賦給self.data,再將self.full_data = self.data,最終返回了self.full_data

  • 序列化元件

    • django原生序列化步驟

      • 匯入模組 from django.core.serializers import serialize

      • 獲取queryset (data = Book.objects.all())

      • 對queryset進行序列化 serialize_data = (serialize("json", data))

      • 將序列化之後的資料,響應給客戶端,返回serialize_data

    • APIView說明
      • APIView繼承了View,並在內部將request重新賦值變成了Request()的例項化物件,因此繼承APIView後

      • 獲取get請求資料要通過,request.query_params

      • 獲取post請求要通過request.data

    • 序列化元件使用

      • Get介面設計(序列化):
        • 宣告序列化器

          • 匯入模組:from rest_framework import serializers

          • 建立一個序列化類 (寫在一個獨立的檔案中): class BookSerializer(serializers.Serializer):欄位自定義,最好跟model的一致

            from rest_framework import serializers
            
            
            class PublishSerializer(serializers.Serializer):
                id = serializers.IntegerField()
                name = serializers.CharField(max_length=32)
            
            
            class AuthorSerializer(serializers.Serializer):
                id = serializers.IntegerField()
                name = serializers.CharField(max_length=32)
            
            
            class BookSerializer(serializers.Serializer):
                id = serializers.IntegerField()
                title = serializers.CharField(max_length=32)
                # 欄位中的通用屬性source可以進行資料庫操作,get_xxx_display取choice欄位的value.(source只能用於序列化)
                category = serializers.CharField(source="get_category_display")
                pub_time = serializers.DateField()
                # 外來鍵,通過id找到publish物件並傳入PublishSerializer(publish物件),最終得到id和title,因為這裡返回的是單個物件,所以不用寫many,而多對多返回的是一個queryset所以多個需要寫many=True,內部會迴圈遍歷
                publish = PublishSerializer()
                # 多對多,記得有many=True
                authors = AuthorSerializer(many=True)
        • 使用自定義的序列化器序列化queryset(將模型物件放入序列化器進行欄位匹配,匹配上的就進行序列化,匹配不上的就丟棄)

        • 序列化完成的資料在序列化返回值的data中(serializer_data.data)

          from django.shortcuts import render,HttpResponse
          from django.views import View
          from django.http import JsonResponse
          
          import json
          
          from rest_framework.views import APIView
          from rest_framework.response import Response
          
          from .models import Book
          from .serializerses import BookSerializer
          
          
          
          class BookView(APIView):
              """書籍相關檢視"""
              def get(self, request):
                  book_queryset = Book.objects.all()
                  # 使用自定義的序列化器序列化queryset
                  serializer_data = BookSerializer(book_queryset, many=True)
                  # 注意這裡是取序列化返回值.data
                  return Response(serializer_data.data)
          注意:外來鍵關係的序列化是巢狀的序列化物件,多對多有many=True,還有如果需要在瀏覽器訪問請求的話需要在settings中INSTALLED_APPS下加入'rest_framework'.
        • 上面是get所有資料,如果是請求單條資料,urls可以設計book/1,這樣的話需要重新定義View,序列化的時候因為是單個book_obj,所以也不用寫many=True了,預設是False

      • Post介面設計(反序列化):
        • 步驟如下:

          • 首先先確定新增的資料結構(跟前端溝通達成一致)

          • 定義序列化器

            • 正序和反序欄位不統一,部分需要重新定義

            • required=False, 只序列化不走校驗

            • read_only=True, 只序列化用

            • write_only=True, 只反序列化用

            • 重寫create方法,引數有驗證通過的資料validated_data

          • 驗證通過返回ser_obj.validated_data,不通過則返回ser_obj.errors

          • 還有些細節注意在程式碼中,請看下面程式碼.

          serializerses.py檔案內容

          from rest_framework import serializers
          
          from app01.models import Book
          
          
          class PublishSerializer(serializers.Serializer):
              id = serializers.IntegerField()
              name = serializers.CharField(max_length=32)
          
          
          class AuthorSerializer(serializers.Serializer):
              id = serializers.IntegerField()
              name = serializers.CharField(max_length=32)
          
          
          class BookSerializer(serializers.Serializer):
              id = serializers.IntegerField(required=False)
              title = serializers.CharField(max_length=32)
              # 欄位中的通用屬性source可以進行資料庫操作,get_xxx_display取choice欄位的value
              category = serializers.CharField(source="get_category_display", read_only=True)
              pub_time = serializers.DateField()
              # 外來鍵
              publish = PublishSerializer(read_only=True)
              # 多對多,記得有many=True
              authors = AuthorSerializer(many=True, read_only=True)
          
              category_id = serializers.IntegerField(write_only=True)
              publish_id = serializers.IntegerField(write_only=True)
              # 多對多傳入的是[],所以用ListField
              author_list = serializers.ListField(write_only=True)
          
              # 將通過驗證的validated_data資料插入資料庫
              def create(self, validated_data):
                  # 先將作者列表從字典中剔除
                  author_list = validated_data.pop('author_list')
                  book_obj = Book.objects.create(title=validated_data['title'],category=validated_data['category_id'], pub_time = validated_data['pub_time'],publish_id=validated_data['publish_id'])
                  book_obj.authors.add(*author_list)
                  return book_obj

          views.py內容

          from django.shortcuts import render,HttpResponse
          
          from rest_framework.views import APIView
          from rest_framework.response import Response
          
          from .models import Book
          from .serializerses import BookSerializer
          '''
          前端提交的格式如下:
          {
                  "title": "西遊1",
                  "category_id": 3,
                  "pub_time": "2019-04-22",
                  "publish_id": 1,
                  "author_list": [1,2]
              }
          '''
          
          class BookView(APIView):
              """書籍相關檢視"""
              def get(self, request):
                  book_queryset = Book.objects.all()
                  serializer_data = BookSerializer(book_queryset, many=True)
                  return Response(serializer_data.data)
          
              def post(self, request):
                  # 獲取前端提交的請求資料
                  book_obj = request.data
                  # 通過data來表示是反序列化
                  ser_obj = BookSerializer(data=book_obj)
                  # 資料校驗
                  if ser_obj.is_valid():
                      # save()方法返回self.instance = self.create(validated_data)
                      # 然後就會呼叫BookSerializer中create方法儲存通過校驗的資料
                      # 最後 save()會返回self.instance,而self.instance就是create的返回資料book_obj(插入的書本物件)
                      ser_obj.save()
                      # 給前端返回新增成功的資料
                      return Response(ser_obj.validated_data)
                  return Response(ser_obj.errors)
        • Put介面和單個get設計:
          • 通過book/id去請求

          在views中,記得put需要傳入要修改的book物件,還有修改的資料和設定部分欄位校驗partial=True.並且定義update方法

          class BookEditView(APIView):
              def get(self, request, book_id):
                  book_obj = Book.objects.filter(id=book_id).first()
                  ser_obj = BookSerializer(book_obj)
                  return Response(ser_obj.data)
          
              def put(self, request, book_id):
                  book_obj = Book.objects.filter(id=book_id).first()
                  # 針對單本書籍物件進行修改,所以instance是單本書籍物件,data是傳入的書籍資訊,partial為True表示只校驗提交的欄位,也就是部分欄位校驗
                  ser_obj = BookSerializer(instance=book_obj, data=request.data, partial=True)
                  if ser_obj.is_valid():
                      ser_obj.save()
                      return Response(ser_obj.validated_data)
                  return ser_obj.errors

          在序列化器中定義update方法

           def update(self, instance, validated_data):
                  # instance表示更新的book_obj物件,validated_data是校驗通過的資料
                  # 給每一個欄位逐一賦值,如果沒有就用原來的值
                  instance.title = validated_data.get('title', instance.title)
                  instance.category = validated_data.get('category_id', instance.category)
                  instance.pub_time = validated_data.get('pub_time', instance.pub_time)
                  instance.category = validated_data.get('category_id', instance.category)
                  if validated_data.get('author_list'):
                      instance.authors.set(validated_data.get('author_list'))
                  # 基於物件操作,儲存需要save一下
                  instance.save()
                  return instance
        • 序列化介面欄位校驗
           # 類似於form區域性鉤子驗證,針對單個屬性
              def validate_title(self, value):
                  # 對書籍標題進行驗證,假設標題不能為純數字
                  if value.isdigit():
                      raise serializers.ValidationError("標題不能為純數字!")
                  return value
          
              # 多個屬性校驗
              def validate(self, attrs):
                  # 對標題和出版社進行校驗
                  if attrs['title'].isdigit() or not attrs['publish_id'].isdigit():
                      raise serializers.ValidationError("標題不能為純數字,出版社必須是數字")
                  return attrs

          上面是單個屬性和多個屬性校驗,下面還有個自定義校驗,然後繫結到欄位上的用法

          def my_validata(value):
              # 自定義規則
              if '敏感資訊' in value:
                  raise serializers.ValidationError("標題不能包含敏感資訊")
              return value
          # 通過validators使用,可以有多個自定義校驗規則
           title = serializers.CharField(max_length=32, validators=[my_validata,])

          有沒有發現上面的序列化和反序列化都非常繁瑣,所以我們在真實開發中一般都用下面的這種

      • ModelSerializer序列化及反序列化

        modelSerializers檔案內容

        from rest_framework import serializers
        from app01.models import (
            Book, Publish, Author
        )
        
        
        class PublishModelSerializer(serializers.ModelSerializer):
            class Meta:
                model = Publish
                fields = "__all__"
        
        
        class AuthorModelSerializer(serializers.ModelSerializer):
            class Meta:
                model = Author
                fields = "__all__"
        
        
        class BookModelSerializer(serializers.ModelSerializer):
            # 這裡定義的都是序列化的情況,所以都是read_only=True,而且都是通過自定義方法返回需要展示的欄位
            category_info = serializers.SerializerMethodField(read_only=True)
            publish_info = serializers.SerializerMethodField(read_only=True)
            authors_info = serializers.SerializerMethodField(read_only=True)
        
            # get_欄位名稱,這個是固定搭配,該方法會return值給category_info欄位,而且傳入的是序列化的obj模型物件
            def get_category_info(self, obj):
                # 自定義需要展示的資料,這裡展示課程的中文, choices型別
                return obj.get_category_display()
        
            def get_publish_info(self, obj):
                # 以字典形式展示的是出版社的id和name, 外來鍵型別
                publish_obj = obj.publish
                return {"id":publish_obj.id, "name":publish_obj.name}
        
            def get_authors_info(self, obj):
                # 由於作者可能有多個,所以以列表包含多個字典形式展示,  多對多型別
                authors_querset = obj.authors.all()
                return [{"id":author.id, "name":author.name} for author in authors_querset]
        
            # 反序列化用原生的,部分序列化欄位需要重新定義,記得不要和原生欄位重名
            class Meta:
                model = Book
                fields = "__all__"
                # exclude = ["id"]      # 不包含id,但是fields和exclude只能有一個
                # 欄位是有序的,根據下面的欄位進行排序展示
                # fields = ["id", "pub_time","title", "category_info", "publish_info", "authors_info"]
                # depth=1是深度為1層,也就是把外來鍵關係的第一層找出來,預設不寫是不顯示出版社名稱和作者資訊的,寫了就找出了出版社的所有資訊和作者所有資訊
                # 注意:depth會讓所有的外來鍵關係欄位都變成read_only=True,所以一般少用
                # depth = 1
                read_only_fields = ["id", ]         # 指明只讀欄位,可以寫多個
                # 新增或修改原有引數,反序列化的個別欄位只需要在反序列化看到
                extra_kwargs = {
                    'category': {"write_only": True,},       # key為預設的欄位名稱, value是自定義的引數配置資訊
                    'publish': {"write_only": True},
                    'authors': {"write_only": True},
                }

        views檔案:(跟之前一樣)

        from rest_framework.views import APIView
        from rest_framework.response import Response
        
        from .models import Book
        from .modelSerializers import BookModelSerializer
        
        class BookView(APIView):
            """書籍相關檢視"""
            def get(self, request):
                book_queryset = Book.objects.all()
                serializer_data = BookModelSerializer(book_queryset, many=True)
                return Response(serializer_data.data)
        
            def post(self, request):
                # 獲取前端提交的請求資料
                book_obj = request.data
                # 通過data來表示是反序列化
                ser_obj = BookModelSerializer(data=book_obj)
                # 資料校驗
                if ser_obj.is_valid():
                    # save()方法返回self.instance = self.create(validated_data)
                    # 然後就會呼叫BookSerializer中create方法儲存通過校驗的資料
                    # 最後 save()會返回self.instance,而self.instance就是create的返回資料book_obj(插入的書本物件)
                    ser_obj.save()
                    # 給前端返回新增成功的資料
                    return Response(ser_obj.validated_data)
                return Response(ser_obj.errors)
        
        
        class BookEditView(APIView):
            def get(self, request, book_id):
                book_obj = Book.objects.filter(id=book_id).first()
                ser_obj = BookModelSerializer(book_obj)
                return Response(ser_obj.data)
        
            def put(self, request, book_id):
                book_obj = Book.objects.filter(id=book_id).first()
                # 針對單本書籍物件進行修改,所以instance是單本書籍物件,data是傳入的書籍資訊,partial為True表示只校驗提交的欄位,也就是部分欄位校驗
                ser_obj = BookModelSerializer(instance=book_obj, data=request.data, partial=True)
                if ser_obj.is_valid():
                    ser_obj.save()
                    return Response(ser_obj.validated_data)
                return Response(ser_obj.errors)