DRF框架


Serializers 序列化組件

為什么要用序列化組件

當我們做前后端分離的項目~~我們前后端交互一般都選擇JSON數據格式,JSON是一個輕量級的數據交互格式。

那么我們給前端數據的時候都要轉成json格式,那就需要對我們從數據庫拿到的數據進行序列化。

接下來我們看下django序列化和rest_framework序列化的對比~~

Django的序列化方法

class BooksView(View):
    def get(self, request):
        book_list = Book.objects.values("id", "title", "chapter", "pub_time", "publisher")
        book_list = list(book_list)
        # 如果我們需要取外鍵關聯的字段信息 需要循環獲取外鍵 再去數據庫查然后拼接成我們想要的
        ret = []
        for book in book_list:
            pub_dict = {}
            pub_obj = Publish.objects.filter(pk=book["publisher"]).first()
            pub_dict["id"] = pub_obj.pk
            pub_dict["title"] = pub_obj.title
            book["publisher"] = pub_dict
            ret.append(book)
        ret = json.dumps(book_list, ensure_ascii=False, cls=MyJson)
        return HttpResponse(ret)


# json.JSONEncoder.default()
# 解決json不能序列化時間字段的問題
class MyJson(json.JSONEncoder):
    def default(self, field):
        if isinstance(field, datetime.datetime):
            return field.strftime('%Y-%m-%d %H:%M:%S')
        elif isinstance(field, datetime.date):
            return field.strftime('%Y-%m-%d')
        else:
            return json.JSONEncoder.default(self, field)
.values 序列化結果
from django.core import serializers


# 能夠得到我們要的效果 結構有點復雜
class BooksView(View):
    def get(self, request):
        book_list = Book.objects.all()
        ret = serializers.serialize("json", book_list)
        return HttpResponse(ret)
django serializers

 DRF序列化的方法

首先,我們要用DRF的序列化,就要遵循人家框架的一些標准,

  -- Django我們CBV繼承類是View,現在DRF我們要用APIView

  -- Django中返回的時候我們用HTTPResponse,JsonResponse,render ,DRF我們用Response

為什么這么用~我們之后會詳細講~~我們繼續來看序列化~~

序列化

class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    title = serializers.CharField(max_length=32)
    CHOICES = ((1, "Linux"), (2, "Django"), (3, "Python"))
    chapter = serializers.ChoiceField(choices=CHOICES, source="get_chapter_display")
    pub_time = serializers.DateField()
第一步 聲明序列化類
from rest_framework.views import APIView
from rest_framework.response import Response

class BookView(APIView):
    def get(self, request):
        book_list = Book.objects.all()
        ret = BookSerializer(book_list, many=True)
        return Response(ret.data)
第二步 序列化對象

外鍵關系的序列化

# by gaoxin
from rest_framework import serializers
from .models import Book


class PublisherSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(max_length=32)


class UserSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    name = serializers.CharField(max_length=32)
    age = serializers.IntegerField()


class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(max_length=32)
    CHOICES = ((1, "Linux"), (2, "Django"), (3, "Python"))
    chapter = serializers.ChoiceField(choices=CHOICES, source="get_chapter_display", read_only=True)
    pub_time = serializers.DateField()

    publisher = PublisherSerializer(read_only=True)
    user = UserSerializer(many=True, read_only=True)
外鍵關系的序列化

反序列化

當前端給我們發post的請求的時候~前端給我們傳過來的數據~我們要進行一些校驗然后保存到數據庫~

這些校驗以及保存工作,DRF的Serializer也給我們提供了一些方法了~~

首先~我們要寫反序列化用的一些字段~有些字段要跟序列化區分開~~

Serializer提供了.is_valid()  和.save()方法~~

# serializers.py 文件
class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(max_length=32)
    CHOICES = ((1, "Linux"), (2, "Django"), (3, "Python"))
    chapter = serializers.ChoiceField(choices=CHOICES, source="get_chapter_display", read_only=True)
    w_chapter = serializers.IntegerField(write_only=True)
    pub_time = serializers.DateField()

    publisher = PublisherSerializer(read_only=True)
    user = UserSerializer(many=True, read_only=True)

    users = serializers.ListField(write_only=True)
    publisher_id = serializers.IntegerField(write_only=True)

    def create(self, validated_data):
        book = Book.objects.create(title=validated_data["title"], chapter=validated_data["w_chapter"], pub_time=validated_data["pub_time"],                                  publisher_id=validated_data["publisher_id"])
        book.user.add(*validated_data["users"])
        return book
反序列化serializer.py
class BookView(APIView):
    def get(self, request):
        book_list = Book.objects.all()
        ret = BookSerializer(book_list, many=True)
        return Response(ret.data)

    def post(self, request):
        # book_obj = request.data
        print(request.data)
        serializer = BookSerializer(data=request.data)
        if serializer.is_valid():
            print(12341253)
            serializer.save()
            return Response(serializer.validated_data)
        else:
            return Response(serializer.errors)
反序列化views.py

當前端給我們發送patch請求的時候,前端傳給我們用戶要更新的數據,我們要對數據進行部分驗證~~

class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(max_length=32)
    CHOICES = ((1, "Linux"), (2, "Django"), (3, "Python"))
    chapter = serializers.ChoiceField(choices=CHOICES, source="get_chapter_display", read_only=True)
    w_chapter = serializers.IntegerField(write_only=True)
    pub_time = serializers.DateField()

    publisher = PublisherSerializer(read_only=True)
    user = UserSerializer(many=True, read_only=True)

    users = serializers.ListField(write_only=True)
    publisher_id = serializers.IntegerField(write_only=True)

    def create(self, validated_data):
        book = Book.objects.create(title=validated_data["title"], chapter=validated_data["w_chapter"], pub_time=validated_data["pub_time"],
                                   publisher_id=validated_data["publisher_id"])
        book.user.add(*validated_data["users"])
        return book

    def update(self, instance, validated_data):
        instance.title = validated_data.get("title", instance.title)
        instance.chapter = validated_data.get("w_chapter", instance.chapter)
        instance.pub_time = validated_data.get("pub_time", instance.pub_time)
        instance.publisher_id = validated_data.get("publisher_id", instance.publisher_id)
        if validated_data.get("users"):
            instance.user.set(validated_data.get("users"))
        instance.save()
        return instance
PATCH請求serializers.py
class BookView(APIView):
     def patch(self, request):
        print(request.data)
        book_id = request.data["id"]
        book_info = request.data["book_info"]
        book_obj = Book.objects.filter(pk=book_id).first()
        serializer = BookSerializer(book_obj, data=book_info, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        else:
            return Response(serializer.errors)
PATCH請求views.py

 驗證

如果我們需要對一些字段進行自定義的驗證~DRF也給我們提供了鈎子方法~~

class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(max_length=32)
    # 省略了一些字段 跟上面代碼里一樣的
    # 。。。。。
     def validate_title(self, value):
        if "python" not in value.lower():
            raise serializers.ValidationError("標題必須含有Python")
        return value
單個字段的驗證
class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(max_length=32)
    CHOICES = ((1, "Linux"), (2, "Django"), (3, "Python"))
    chapter = serializers.ChoiceField(choices=CHOICES, source="get_chapter_display", read_only=True)
    w_chapter = serializers.IntegerField(write_only=True)
    pub_time = serializers.DateField()
    date_added = serializers.DateField(write_only=True)
    # 新增了一個上架時間字段  
    # 省略一些字段。。都是在原基礎代碼上增加的
    # 。。。。。。

    # 對多個字段進行驗證 要求上架日期不能早於出版日期 上架日期要大
    def validate(self, attrs):
        if attrs["pub_time"] > attrs["date_added"]:
            raise serializers.ValidationError("上架日期不能早於出版日期")
        return attrs
多個字段的驗證
def my_validate(value):
    if "敏感詞匯" in value.lower:
        raise serializers.ValidationError("包含敏感詞匯,請重新提交")
    return value


class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(max_length=32, validators=[my_validate])
    # 。。。。。。
    
驗證器 validators

 ModelSerializer

現在我們已經清楚了Serializer的用法,會發現我們所有的序列化跟我們的模型都緊密相關~

那么,DRF也給我們提供了跟模型緊密相關的序列化器~~ModelSerializer~~

  -- 它會根據模型自動生成一組字段

  -- 它簡單的默認實現了.update()以及.create()方法

定義一個ModelSerializer序列化器

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = "__all__"
        # fields = ["id", "title", "pub_time"]
        # exclude = ["user"]
        # 分別是所有字段 包含某些字段 排除某些字段
定義ModelSerializer

外鍵關系的序列化

注意:當序列化類MATE中定義了depth時,這個序列化類中引用字段(外鍵)則自動變為只讀

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = "__all__"
        # fields = ["id", "title", "pub_time"]
        # exclude = ["user"]
        # 分別是所有字段 包含某些字段 排除某些字段
        depth = 1
# depth 代表找嵌套關系的第幾層
外鍵關系序列化

自定義字段

我們可以聲明一些字段來覆蓋默認字段,來進行自定制~

比如我們的選擇字段,默認顯示的是選擇的key,我們要給用戶展示的是value。

class BookSerializer(serializers.ModelSerializer):
    chapter = serializers.CharField(source="get_chapter_display", read_only=True)
    
    class Meta:
        model = Book
        fields = "__all__"
        # fields = ["id", "title", "pub_time"]
        # exclude = ["user"]
        # 分別是所有字段 包含某些字段 排除某些字段
        depth = 1
自定義字段

Meta中其它關鍵字參數

class BookSerializer(serializers.ModelSerializer):
    chapter = serializers.CharField(source="get_chapter_display", read_only=True)

    class Meta:
        model = Book
        fields = "__all__"
        # fields = ["id", "title", "pub_time"]
        # exclude = ["user"]
        # 分別是所有字段 包含某些字段 排除某些字段
        depth = 1
        read_only_fields = ["id"]
        extra_kwargs = {"title": {"validators": [my_validate,]}}
Meta中參數

post以及patch請求

由於depth會讓我們外鍵變成只讀,所以我們再定義一個序列化的類,其實只要去掉depth就可以了~~

class BookSerializer(serializers.ModelSerializer):
    chapter = serializers.CharField(source="get_chapter_display", read_only=True)

    class Meta:
        model = Book
        fields = "__all__"
        # fields = ["id", "title", "pub_time"]
        # exclude = ["user"]
        # 分別是所有字段 包含某些字段 排除某些字段
        read_only_fields = ["id"]
        extra_kwargs = {"title": {"validators": [my_validate,]}}
post/patch請求序列化類

SerializerMethodField

外鍵關聯的對象有很多字段我們是用不到的~都傳給前端會有數據冗余~就需要我們自己去定制序列化外鍵對象的哪些字段~~

class BookSerializer(serializers.ModelSerializer):
    chapter = serializers.CharField(source="get_chapter_display", read_only=True)
    user = serializers.SerializerMethodField()
    publisher = serializers.SerializerMethodField()

    def get_user(self, obj):
        # obj是當前序列化的book對象
        users_query_set = obj.user.all()
        return [{"id": user_obj.pk, "name": user_obj.name} for user_obj in users_query_set]

    def get_publisher(self, obj):
        publisher_obj = obj.publisher
        return {"id": publisher_obj.pk, "title": publisher_obj.title}

    class Meta:
        model = Book
        fields = "__all__"
        # fields = ["id", "title", "pub_time"]
        # exclude = ["user"]
        # 分別是所有字段 包含某些字段 排除某些字段
        read_only_fields = ["id"]
        extra_kwargs = {"title": {"validators": [my_validate,]}}
SerializerMethodField

用ModelSerializer改進上面Serializer的完整版

class BookSerializer(serializers.ModelSerializer):
    dis_chapter = serializers.SerializerMethodField(read_only=True)
    users = serializers.SerializerMethodField(read_only=True)
    publishers = serializers.SerializerMethodField(read_only=True)

    def get_users(self, obj):
        # obj是當前序列化的book對象
        users_query_set = obj.user.all()
        return [{"id": user_obj.pk, "name": user_obj.name} for user_obj in users_query_set]

    def get_publishers(self, obj):
        publisher_obj = obj.publisher
        return {"id": publisher_obj.pk, "title": publisher_obj.title}

    def get_dis_chapter(self, obj):
        return obj.get_chapter_display()

    class Meta:
        model = Book
        # fields = "__all__"
        # 字段是有序的
        fields = ["id", "title","dis_chapter", "pub_time", "publishers", "users","chapter", "user", "publisher"]
        # exclude = ["user"]
        # 分別是所有字段 包含某些字段 排除某些字段
        read_only_fields = ["id", "dis_chapter", "users", "publishers"]
        extra_kwargs = {"title": {"validators": [my_validate,]}, "user": {"write_only": True}, "publisher": {"write_only": True},
                        "chapter": {"write_only": True}}
ModelSerializer

 

Django Rest Framework 視圖和路由

DRF的視圖

APIView

我們django中寫CBV的時候繼承的是View,rest_framework繼承的是APIView,那么他們兩個有什么不同呢~~~

?
urlpatterns = [
     url(r '^book$' , BookView.as_view()),
     url(r '^book/(?P<id>\d+)$' , BookEditView.as_view()),
]

我們可以看到,不管是View還是APIView最開始調用的都是as_view()方法~~那我們走進源碼看看~~

我們能看到,APIView繼承了View, 並且執行了View中的as_view()方法,最后把view返回了,用csrf_exempt()方法包裹后去掉了csrf的認證。

那我們看看View中的as_view()方法做了什么~

我們看到了~在View中的as_view方法返回了view函數,而view函數執行了self.dispatch()方法~~但是這里的dispatch方法應該是我們APIView中的~~

我們去initialize_request中看下把什么賦值給了request,並且賦值給了self.request, 也就是我們在視圖中用的request.xxx到底是什么~~

我們看到,這個方法返回的是Request這個類的實例對象~~我們注意我們看下這個Request類中的第一個參數request,是我們走我們django的時候的原來的request~

我們看到了,這個Request類把原來的request賦值給了self._request, 也就是說以后_request是我們老的request,新的request是我們這個Request類~~

那我們繼承APIView之后請求來的數據都在哪呢~~

我們用了rest_framework框架以后,我們的request是重新封裝的Request類~

request.query_params 存放的是我們get請求的參數

request.data 存放的是我們所有的數據,包括post請求的以及put,patch請求~~~

相比原來的django的request,我們現在的request更加精簡,清晰了~~~

現在我們知道了APIView和View的一些區別~~當然還有~~后面我們還會說~~

我們寫的視圖可能對多個表進行增刪改查,就導致我們的視圖特別多重復的代碼~~

那么我們嘗試着來進行封裝一下~~

第一次封裝

class BookView(APIView):

    def get(self, request):
        query_set = Book.objects.all()
        book_ser = BookSerializer(query_set, many=True)
        return Response(book_ser.data)

    def post(self, request):
        query_set = request.data
        book_ser = BookSerializer(data=query_set)
        if book_ser.is_valid():
            book_ser.save()
            return Response(book_ser.validated_data)
        else:
            return Response(book_ser.errors)


class BookEditView(APIView):

    def get(self, request, id):
        query_set = Book.objects.filter(id=id).first()
        book_ser = BookSerializer(query_set)
        return Response(book_ser.data)

    def patch(self, request, id):
        query_set = Book.objects.filter(id=id).first()
        book_ser = BookSerializer(query_set, data=request.data, partial=True)
        if book_ser.is_valid():
            book_ser.save()
            return Response(book_ser.validated_data)
        else:
            return Response(book_ser.errors)

    def delete(self, request, id):
        query_set = Book.objects.filter(id=id).first()
        if query_set:
            query_set.delete()
            return Response("")
        else:
            return Response("刪除的書籍不存在")
APIView視圖
class GenericAPIView(APIView):
    queryset = None
    serializer_class = None

    def get_queryset(self):
        return self.queryset.all()

    def get_serializer(self, *args, **kwargs):
        return self.serializer_class(*args, **kwargs)


class ListModelMixin(object):
    def list(self, request, *args, **kwargs):
        queryset = self.get_queryset()
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)


class CreateModelMixin(object):
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.validated_data)
        else:
            return Response(serializer.errors)


class RetrieveModelMixin(object):
    def retrieve(self, request, id, *args, **kwargs):
        book_obj = self.get_queryset().filter(pk=id).first()
        book_ser = self.get_serializer(book_obj)
        return Response(book_ser.data)


class UpdateModelMixin(object):
    def update(self, request, id, *args, **kwargs):
        book_obj = self.get_queryset().filter(pk=id).first()
        book_ser = self.get_serializer(book_obj, data=request.data, partial=True)
        if book_ser.is_valid():
            book_ser.save()
            return Response(book_ser.validated_data)
        else:
            return Response(book_ser.errors)


class DestroyModelMixin(object):
    def destroy(self, request, id, *args, **kwargs):
        queryset = self.get_queryset()
        try:
            queryset.get(pk=id).delete()
            return Response("")
        except Exception as e:
            return Response("信息有誤")
# 我們把公共的部分抽出來 這樣不管寫多少表的增刪改查都變的很簡單
# 這樣封裝后我們的視圖會變成這樣

class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)


class BookEditView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def get(self, request, id, *args, **kwargs):
        return self.retrieve(request, id, *args, **kwargs)

    def patch(self, request, id, *args, **kwargs):
        return self.update(request, id, *args, **kwargs)

    def destroy(self, request, id, *args, **kwargs):
        return self.delete(request, id, *args, **kwargs)
第一次封裝

我們封裝的GenericAPIView,包括封裝每個方法的類,其實框架都幫我們封裝好了~~

我們可以直接繼承這些類~~來實現上面的視圖~~可是還有沒有更簡單的方法呢~我們再次封裝一下~~

第二次封裝

# 上面我們寫的繼承類太長了~~我們再改改

class ListCreateAPIView(GenericAPIView, ListModelMixin, CreateModelMixin):
    pass


class RetrieveUpdateDestroyAPIView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
    pass


class BookView(ListCreateAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)


class BookEditView(RetrieveUpdateDestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def get(self, request, id, *args, **kwargs):
        return self.retrieve(request, id, *args, **kwargs)

    def patch(self, request, id, *args, **kwargs):
        return self.update(request, id, *args, **kwargs)

    def delete(self, request, id, *args, **kwargs):
        return self.delete(request, id, *args, **kwargs)
第二次封裝

這次我們只是讓繼承變的簡單了一點而已,好像並沒有什么大的進步~~

我們可不可以把這兩個視圖合並成一個視圖呢~~~框架給我們提供了一個路由傳參的方法~~

我們看下ViewSetMixin

actions這個默認參數其實就是我們路由可以進行傳參了~~~

下面這個循環~可以看出~我們要傳的參數是一個字段~key應該是我們的請求方式,value應該對應我們處理的方法~

這樣我們每個視圖就不用在寫函數了~因為已經和內部實現的函數相對應了~

第三次封裝

urlpatterns = [
    # url(r'^book$', BookView.as_view()),
    # url(r'^book/(?P<id>\d+)$', BookEditView.as_view()),
    url(r'^book$', BookView.as_view({"get": "list", "post": "create"})),
    url(r'^book/(?P<pk>\d+)$', BookView.as_view({"get": "retrieve", "patch": "update", "delete": "destroy"})),
]
路由urls.py
from rest_framework.viewsets import ViewSetMixin


# class BookView(ViewSetMixin, ListCreateAPIView, RetrieveUpdateDestroyAPIView):
#     queryset = Book.objects.all()
#     serializer_class = BookSerializer
    
    
# 如果我們再定義一個類
class ModelViewSet(ViewSetMixin, ListCreateAPIView, RetrieveUpdateDestroyAPIView):
    pass


class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
第三次封裝

 我們現在的視圖就只要寫兩行就可以了~~~

其實我們寫的所有的視圖~框架都幫我們封裝好了~

注意一點~~用框架封裝的視圖~我們url上的那個關鍵字參數要用pk~~系統默認的~~

奉獻一張圖來看下我們的繼承順序~~~

DRF的路由

我們上面的路由傳參寫的特別多~~框架也幫我們封裝好了~

from .views import BookView
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r"book", BookView)

urlpatterns = [
    # url(r'^book$', BookView.as_view()),
    # url(r'^book/(?P<id>\d+)$', BookEditView.as_view()),
    # url(r'^book$', BookView.as_view({"get": "list", "post": "create"})),
    # url(r'^book/(?P<pk>\d+)$', BookView.as_view({"get": "retrieve", "patch": "update", "delete": "destroy"})),

]
urlpatterns += router.urls
路由

我們可以看到~~通過框架我們可以把路由視圖都變的非常簡單~~

但是需要自定制的時候還是需要我們自己用APIView寫~~當不需要那么多路由的時候~也不要用這種路由注冊~~

總之~~一切按照業務需要去用~~~ 

 

DRF 版本 認證

 

DRF的版本

版本控制是做什么用的, 我們為什么要用

首先我們要知道我們的版本是干嘛用的呢~~大家都知道我們開發項目是有多個版本的~~

當我們項目越來越更新~版本就越來越多~~我們不可能新的版本出了~以前舊的版本就不進行維護了~~~

那我們就需要對版本進行控制~~這個DRF也給我們提供了一些封裝好的版本控制方法~~

版本控制怎么用

之前我們學視圖的時候知道APIView,也知道APIView返回View中的view函數,然后調用的dispatch方法~

那我們現在看下dispatch方法~~看下它都做了什么~~

執行self.initial方法之前是各種賦值,包括request的重新封裝賦值,下面是路由的分發,那我們看下這個方法都做了什么~~

我們可以看到,我們的version版本信息賦值給了 request.version  版本控制方案賦值給了 request.versioning_scheme~~

其實這個版本控制方案~就是我們配置的版本控制的類~~

也就是說,APIView通過這個方法初始化自己提供的組件~~

我們接下來看看框架提供了哪些版本的控制方法~~在rest_framework.versioning里~~

框架一共給我們提供了這幾個版本控制的方法~~我們在這里只演示一個~~因為基本配置都是一樣的~~

詳細用法

我們看下放在URL上攜帶版本信息怎么配置~~

REST_FRAMEWORK = {
    # 默認使用的版本控制類
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
    # 允許的版本
    'ALLOWED_VERSIONS': ['v1', 'v2'],
    # 版本使用的參數名稱
    'VERSION_PARAM': 'version',
    # 默認使用的版本
    'DEFAULT_VERSION': 'v1',
}
第一步 setting.py
urlpatterns = [
    url(r"^versions", MyView.as_view()),
    url(r"^(?P<version>[v1|v2]+)/test01", TestView.as_view()),
]
第二步 urls.py
class TestView(APIView):
    def get(self, request, *args, **kwargs):
        print(request.versioning_scheme)
        ret = request.version
        if ret == "v1":
            return Response("版本v1的信息")
        elif ret == "v2":
            return Response("版本v2的信息")
        else:
            return Response("根本就匹配不到這個路由")
測試視圖

其他的版本控制的類,配置方法都差不多~~這里就不一一例舉了~~

DRF的認證

認證是干嘛的呢~

我們都知道~我們可以在網站上登錄~然后可以有個人中心,對自己信息就行修改~~~

但是我們每次給服務器發請求,由於Http的無狀態,導師我們每次都是新的請求~~

那么服務端需要對每次來的請求進行認證,看用戶是否登錄,以及登錄用戶是誰~~

那么我們服務器對每個請求進行認證的時候,不可能在每個視圖函數中都寫認證~~~

一定是把認證邏輯抽離出來~~以前我們可能會加裝飾器~或者中間件~~那我們看看DRF框架給我們提供了什么~~~

認證怎么用

上面講版本的時候我們知道~在dispatch方法里~執行了initial方法~~那里初始化了我們的版本~~

如果我們細心我們能看到~版本的下面其實就是我們的認證,權限,頻率組件了~~

我們先看看我們的認證組件~~

我們進去我們的認證看下~~

我們這個權限組件返回的是request.user,那我們這里的request是新的還是舊的呢~~

我們的initial是在我們request重新賦值之后的~所以這里的request是新的~也就是Request類實例對象~~

那這個user一定是一個靜態方法~我們進去看看~~

我沒在這里反復的截圖跳轉頁面~~大家可以嘗試着自己去找~~要耐心~~細心~~

我們通過上面基本可以知道我們的認證類一定要實現的方法~~以及返回值類型~~以及配置的參數authentication_classes~

下面我們來看看具體用法~~~

認證的詳細用法

我們先寫個認證的小demo~~我們先建一個用戶表~字段為用戶名以及對應的token值~~

# 先在model中注冊模型類
# 並且進行數據遷移
# 測試我就簡寫了~

class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    token = models.UUIDField()
models.py
# 寫視圖類並且用post請求注冊一個用戶

class UserView(APIView):
    def post(self, request, *args, **kwargs):
        username = request.data["username"]
        UserInfo.objects.create(username=username, token=uuid.uuid4())
        return Response("注冊成功")
views.py

准備工作完成~我們來開始我們的認證~~

# 注意我們這個認證的類必須實現的方法以及返回值
class MyAuth(BaseAuthentication):

    def authenticate(self, request):
        request_token = request.query_params.get("token", None)
        if not request_token:
            raise AuthenticationFailed({"code": 1001, "error": "缺少token"})
        token_obj = UserInfo.objects.filter(token=request_token).first()
        if not token_obj:
            raise AuthenticationFailed({"code": 1001, "error": "無效的token"})
        return token_obj.username, token_obj
寫一個認證的類
class TestAuthView(APIView):
    authentication_classes = [MyAuth, ]

    def get(self, request, *args, **kwargs):
        return Response("測試認證")
視圖級別認證
REST_FRAMEWORK = {
    # 默認使用的版本控制類
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
    # 允許的版本
    'ALLOWED_VERSIONS': ['v1', 'v2'],
    # 版本使用的參數名稱
    'VERSION_PARAM': 'version',
    # 默認使用的版本
    'DEFAULT_VERSION': 'v1',
    # 配置全局認證
    'DEFAULT_AUTHENTICATION_CLASSES': ["BRQP.utils.MyAuth", ]
}
全局配置認證

 

DRF 權限 頻率

DRF的權限 

權限是什么

大家之前都應該聽過權限~那么我們權限到底是做什么用的呢~~

大家都有博客~或者去一些論壇~一定知道管理員這個角色~

比如我們申請博客的時候~一定要向管理員申請~也就是說管理員會有一些特殊的權利~是我們沒有的~~

這些對某件事情決策的范圍和程度~我們叫做權限~~權限是我們在項目開發中非常常用到的~~

那我們看DRF框架給我們提供的權限組件都有哪些方法~~

權限組件源碼

我們之前說過了DRF的版本和認證~也知道了權限和頻率跟版本認證都是在initial方法里初始化的~~

其實我們版本,認證,權限,頻率控制走的源碼流程大致相同~~大家也可以在源碼里看到~~

我們的權限類一定要有has_permission方法~否則就會拋出異常~~這也是框架給我提供的鈎子~~

我們先看到在rest_framework.permissions這個文件中~存放了框架給我們提供的所有權限的方法~~

我這里就不帶着大家詳細去看每一個了~大家可以去瀏覽一下每個權限類~看看每個都是干嘛的~~

這里主要說下BasePermission 這個是我們寫權限類繼承的一個基礎權限類~~~ 

權限的詳細用法

在這里我們一定要清楚一點~我們的Python代碼是一行一行執行的~那么執行initial方法初始化這些組件的時候~~

也是有順序的~~我們的版本在前面~然后是認證,然后是權限~ 最后是頻率~~所以大家要清楚~~

我們的權限執行的時候~我們的認證已經執行結束了~~~

前提在model中的UserInfo表中加了一個字段~用戶類型的字段~~做好數據遷移~~

class MyPermission(BasePermission):
    message = "VIP用戶才能訪問"

    def has_permission(self, request, view):
        """
        自定義權限只有vip用戶能訪問,
        注意我們初始化時候的順序是認證在權限前面的,所以只要認證通過~
        我們這里就可以通過request.user,拿到我們用戶信息
        request.auth就能拿到用戶對象
        """
        if request.user and request.auth.type == 2:
            return True
        else:
            return False
第一步 寫權限類
class TestAuthView(APIView):
    authentication_classes = [MyAuth, ]
    permission_classes = [MyPermission, ]

    def get(self, request, *args, **kwargs):
        print(request.user)
        print(request.auth)
        username = request.user
        return Response(username)
局部視圖注冊
REST_FRAMEWORK = {
    # 默認使用的版本控制類
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
    # 允許的版本
    'ALLOWED_VERSIONS': ['v1', 'v2'],
    # 版本使用的參數名稱
    'VERSION_PARAM': 'version',
    # 默認使用的版本
    'DEFAULT_VERSION': 'v1',
    # 配置全局認證
    # 'DEFAULT_AUTHENTICATION_CLASSES': ["BRQP.utils.MyAuth", ]
    # 配置全局權限
    "DEFAULT_PERMISSION_CLASSES": ["BROP.utils.MyPermission"]
}
全局注冊 settings.py

DRF的頻率

頻率限制是做什么的

開放平台的API接口調用需要限制其頻率,以節約服務器資源和避免惡意的頻繁調用。

我們的DRF提供了一些頻率限制的方法,我們看一下。

頻率組件源碼

版本,認證,權限,頻率這幾個組件的源碼是一個流程,這里就不再帶大家走源碼了~

相信大家可以自己看懂了~~下面我們談談頻率組件實現的原理~~

頻率組件原理

DRF中的頻率控制基本原理是基於訪問次數和時間的,當然我們可以通過自己定義的方法來實現。

當我們請求進來,走到我們頻率組件的時候,DRF內部會有一個字典來記錄訪問者的IP,

以這個訪問者的IP為key,value為一個列表,存放訪問者每次訪問的時間,

{  IP1: [第三次訪問時間,第二次訪問時間,第一次訪問時間],}

把每次訪問最新時間放入列表的最前面,記錄這樣一個數據結構后,通過什么方式限流呢~~

如果我們設置的是10秒內只能訪問5次,

  -- 1,判斷訪問者的IP是否在這個請求IP的字典里

  -- 2,保證這個列表里都是最近10秒內的訪問的時間

      判斷當前請求時間和列表里最早的(也就是最后的)請求時間的查

      如果差大於10秒,說明請求以及不是最近10秒內的,刪除掉,

      繼續判斷倒數第二個,直到差值小於10秒

  -- 3,判斷列表的長度(即訪問次數),是否大於我們設置的5次,

      如果大於就限流,否則放行,並把時間放入列表的最前面。

頻率組件的詳細用法

頻率組件的配置方式其實跟上面的組件都一樣,我們看下頻率組件的使用。

VISIT_RECORD = {}
class MyThrottle(object):

    def __init__(self):
        self.history = None

    def allow_request(self, request, view):
        """
        自定義頻率限制60秒內只能訪問三次
        """
        # 獲取用戶IP
        ip = request.META.get("REMOTE_ADDR")
        timestamp = time.time()
        if ip not in VISIT_RECORD:
            VISIT_RECORD[ip] = [timestamp, ]
            return True
        history = VISIT_RECORD[ip]
        self.history = history
        history.insert(0, timestamp)
        while history and history[-1] < timestamp - 60:
            history.pop()
        if len(history) > 3:
            return False
        else:
            return True

    def wait(self):
        """
        限制時間還剩多少
        """
        timestamp = time.time()
        return 60 - (timestamp - self.history[-1])
自定義的頻率限制類
REST_FRAMEWORK = {
    # ......
    # 頻率限制的配置
    "DEFAULT_THROTTLE_CLASSES": ["Throttle.throttle.MyThrottle"],
    }
}
配置自定義頻率限制
from rest_framework.throttling import SimpleRateThrottle


class MyVisitThrottle(SimpleRateThrottle):
    scope = "WD"

    def get_cache_key(self, request, view):
        return self.get_ident(request)
使用自帶的頻率限制類
REST_FRAMEWORK = {
    # 頻率限制的配置
    # "DEFAULT_THROTTLE_CLASSES": ["Throttle.throttle.MyVisitThrottle"],
    "DEFAULT_THROTTLE_CLASSES": ["Throttle.throttle.MyThrottle"],
    "DEFAULT_THROTTLE_RATES":{
        'WD':'5/m',         #速率配置每分鍾不能超過5次訪問,WD是scope定義的值,

    }
}
配置頻率限制

 我們可以在postman~~或者DRF自帶的頁面進行測試都可以~~

 

DRF的分頁

DRF的分頁

為什么要使用分頁

其實這個不說大家都知道,大家寫項目的時候也是一定會用的,

我們數據庫有幾千萬條數據,這些數據需要展示,我們不可能直接從數據庫把數據全部讀取出來,

這樣會給內存造成特別大的壓力,有可能還會內存溢出,所以我們希望一點一點的取,

那展示的時候也是一樣的,總是要進行分頁顯示,我們之前自己都寫過分頁。

那么大家想一個問題,在數據量特別大的時候,我們的分頁會越往后讀取速度越慢,

當有一千萬條數據,我要看最后一頁的內容的時候,怎么能讓我的查詢速度變快。

DRF給我們提供了三種分頁方式,我們看下他們都是什么樣的~~

分頁組件的使用

DRF提供的三種分頁

 

from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination

 

全局配置

 

REST_FRAMEWORK = {
    'PAGE_SIZE': 2
}

 

第一種 PageNumberPagination  看第n頁,每頁顯示n條數據

http://127.0.0.1:8000/book?page=2&size=1

class MyPageNumber(PageNumberPagination):
    page_size = 2  # 每頁顯示多少條
    page_size_query_param = 'size'  # URL中每頁顯示條數的參數
    page_query_param = 'page'  # URL中頁碼的參數
    max_page_size = None  # 最大頁碼數限制
自定義分頁類
class BookView(APIView):
    def get(self, request):
        book_list = Book.objects.all()
        # 分頁
        page_obj = MyPageNumber()
        page_article = page_obj.paginate_queryset(queryset=book_list, request=request, view=self)
        
        ret = BookSerializer(page_article, many=True)
        return Response(ret.data)
視圖
class BookView(APIView):
    def get(self, request):
        book_list = Book.objects.all()
        # 分頁
        page_obj = MyPageNumber()
        page_article = page_obj.paginate_queryset(queryset=book_list, request=request, view=self)

        ret = BookSerializer(page_article, many=True)
        # return Response(ret.data)
        # 返回帶超鏈接 需返回的時候用內置的響應方法
        return page_obj.get_paginated_response(ret.data)
返回帶頁碼鏈接的響應

第二種 LimitOffsetPagination 在第n個位置  向后查看n條數據

http://127.0.0.1:8000/book?offset=2&limit=1

class MyLimitOffset(LimitOffsetPagination):
    default_limit = 1
    limit_query_param = 'limit'
    offset_query_param = 'offset'
    max_limit = 999
自定義的分頁類
# 視圖和上面的大體一致
# 只有用的分頁類不同,其他都相同
class BookView(APIView):
    def get(self, request):
        book_list = Book.objects.all()
        # 分頁
        page_obj = MyLimitOffset()
        page_article = page_obj.paginate_queryset(queryset=book_list, request=request, view=self)

        ret = BookSerializer(page_article, many=True)
        # return Response(ret.data)
        # 返回帶超鏈接 需返回的時候用內置的響應方法
        return page_obj.get_paginated_response(ret.data)
視圖

第三種 CursorPagination 加密游標的分頁 把上一頁和下一頁的id記住

class MyCursorPagination(CursorPagination):
    cursor_query_param = 'cursor'
    page_size = 1
    ordering = '-id'
自定義分頁類
class BookView(APIView):
    def get(self, request):
        book_list = Book.objects.all()
        # 分頁
        page_obj = MyCursorPagination()
        page_article = page_obj.paginate_queryset(queryset=book_list, request=request, view=self)

        ret = BookSerializer(page_article, many=True)
        # return Response(ret.data)
        # 返回帶超鏈接 需返回的時候用內置的響應方法
        return page_obj.get_paginated_response(ret.data)
視圖

DRF的解析器和渲染器

解析器

解析器的作用就是服務端接收客戶端傳過來的數據,把數據解析成自己想要的數據類型的過程。

本質就是對請求體中的數據進行解析。

Django的解析器

我們請求進來請求體中的數據在request.body中,那也就證明,解析器會把解析好的數據放入request.body

我們在視圖中可以打印request的類型,能夠知道request是WSGIRequest這個類。

我們可以看下這個類的源碼~~~我們是怎么拿到request.POST數據的~~

 

application/x-www-form-urlencoded不是不能上傳文件,是只能上傳文本格式的文件,

multipart/form-data是將文件以二進制的形式上傳,這樣可以實現多種類型的文件上傳

一個解析到request.POST,   request.FILES中。

也就是說我們之前能在request中能到的各種數據是因為用了不同格式的數據解析器~

那么我們的DRF能夠解析什么樣的數據類型呢~~~

DRF的解析器

我們想一個問題~什么時候我們的解析器會被調用呢~~ 是不是在request.data拿數據的時候~

我們說請求數據都在request.data中,那我們看下這個Request類里的data~~

得到解析器后,調用解析器里的parse方法~~

 那說到這里~我們看下DRF配置的默認的解析器的類都有哪些~~

也就是說我們的DRF支持Json,Form表單的請求,包括多種文件類型的數據~~~~

可以在我們的視圖中配置視圖級別的解析器~~~

這就是我們DRF的解析器~~~

DRF的渲染器

渲染器就是友好的展示數據~~

DRF給我們提供的渲染器有~~

我們在瀏覽器中展示的DRF測試的那個頁面~就是通過瀏覽器的渲染器來做到的~~

當然我們可以展示Json數據類型~~~~渲染器比較簡單~~~~ 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM