Django REST framework學習筆記


個人網站: https://pythoneers.cn

1. API接口開發
1.1 獲取數據的接口

獲取全部的字段:前端

from rest_framework import views, serializers, response
from .. import models

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.User
        fields = "__all__"

class User(views.APIView):
    def get(self, request):
        qs = models.User.objects.all()
        serializer = UserSerializer(qs, many=True)
        return response.Response({ 
 
   
            'status': 0,
            'data': serializer.data
        })

在這裏插入圖片描述
獲取部分字段:python

from rest_framework import views, serializers, response
from .. import models

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.User
        fields = ('id', 'name') # 只須要修改這裏

class User(views.APIView):
    def get(self, request):
        qs = models.User.objects.all()
        serializer = UserSerializer(qs, many=True)
        return response.Response({ 
 
   
            'status': 0,
            'data': serializer.data
        })

在這裏插入圖片描述
獲取單條數據:mysql

from rest_framework import views, serializers, response
from .. import models

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.User
        fields = ('id', 'name')

class User(views.APIView):
    def get(self, request):
        qs = models.User.objects.all()
        serializer = UserSerializer(qs.first(), many=False)  # 默認是False,修改這裏
        return response.Response({ 
 
   
            'status': 0,
            'data': serializer.data
        })

在這裏插入圖片描述

1.2 添加數據的接口

添加數據:web

from rest_framework import views, serializers, response
from .. import models

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.User
        fields = "__all__"

class User(views.APIView):
    def post(self, request):
        serializer = UserSerializer(data=request.data)
        result = { 
 
   'status': 0, 'data': None}
        if serializer.is_valid():
            instance = serializer.save()
            result['data'] = instance.pk
            return response.Response(result)
        else:
        	result['status'] = 1
            result['data'] = serializer.errors
        return result

在這裏插入圖片描述

1.3 更新數據的接口

更新數據的接口:ajax

from rest_framework import views, serializers, response
from .. import models

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.User
        fields = "__all__"
        
class UserDetail(views.APIView):
    def put(self, request, pk):
        instance = models.User.objects.filter(pk=pk).first()
        result = { 
 
   'status': 0, 'data': None}
        if not instance:
            result['status'] = 1
            result['data'] = '數據不存在!'
        else:
            # 若是沒有傳入instance,默認是None,表示添加數據而不是更新數據
            serializer = UserSerializer(instance=instance, data=request.data)
            if serializer.is_valid():
                instance = serializer.save()
                result['data'] = instance.pk
            else:
                result['status'] = 1
                result['data'] = serializer.errors
        return response.Response(result)

沒有查詢到要更新的數據:
在這裏插入圖片描述
更新數據成功:
在這裏插入圖片描述sql

1.4 刪除數據的接口

刪除數據的接口:數據庫

from rest_framework import views, serializers, response
from .. import models

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.User
        fields = "__all__"
        
class UserDetail(views.APIView):
    def delete(self, request, pk):
        instance = models.User.objects.filter(pk=pk)
        result = { 
 
   'status': 0, 'data': None}
        if not instance:
            result['status'] = 1
            result['data'] = '數據不存在,沒法刪除!'
        else:
            instance.delete()
            result['data'] = []
        return response.Response(result)

刪除的數據不存在:
在這裏插入圖片描述
刪除數據成功:
在這裏插入圖片描述django

2. API字段的定製

表中的字段有時候是不能知足咱們實際須要的,因此大多狀況下咱們須要 加工從數據庫中獲取到的字段,以及擴展更多的字段。也可能獲取別的表的字段進行數據加工生成新字段,響應給前端讓它作數據渲染。下面是對字段處理的幾種方式:json

2.1 別名字段

這種是最簡單的,返回給客戶端的字段和原字段的字段名不一樣,可是 值相同後端

class UserSerializer(serializers.ModelSerializer):
    name = serializers.CharField(max_length=5, error_messages={ 
 
   'max_length': '字段太長!'})
    alias = serializers.CharField(source='name') # 注意這裏source='name'

    class Meta:
        model = models.User
        fields = ('name', 'alias')
        
class User(views.APIView):
    def get(self, request):
        qs = models.User.objects.all()
        serializer = UserSerializer(qs, many=True)
        return response.Response({ 
 
   
            'status': 0,
            'data': serializer.data
        })

在這裏插入圖片描述

2.2 字段格式化

對從數據庫中獲取的日起進行格式化,數據庫中格式是:2020-06-20 03:32:28.376772,格式化以後是:2020-06-20 03:32:28,

class UserSerializer(serializers.ModelSerializer):
    name = serializers.CharField(max_length=5, error_messages={ 
 
   'max_length': '字段太長!'})
    alias = serializers.CharField(source='name')
    register_date = serializers.DateTimeField(format='%Y-%m-%d %X') # 在這裏進行的格式化

    class Meta:
        model = models.User
        fields = ('name', 'alias', 'register_date')

class User(views.APIView):
    def get(self, request):
        qs = models.User.objects.all()
        serializer = UserSerializer(qs, many=True)
        return response.Response({ 
 
   
            'status': 0,
            'data': serializer.data
        })

在這裏插入圖片描述

2.3 字段建立

字段別名的方式能夠認爲是建立新的字段,可是它仍是依賴於原字段,不是真正意義上的建立新字段:

class UserSerializer(serializers.ModelSerializer):
    name = serializers.CharField(max_length=5, error_messages={ 
 
   'max_length': '字段太長!'})
    alias = serializers.CharField(source='name')
    register_date = serializers.DateTimeField(format='%Y-%m-%d %X')
    """ 建立新字段,自定義名字是id """
    # id = serializers.SerializerMethodField(method_name='get_id') # 能夠指定使用的方法名稱
    id = serializers.SerializerMethodField()  # 能夠不指定默認是:get_id,須要寫get_id方法
    
    def get_id(self, instance):
        """ :param instance: 查詢的結果(直接寫上instance就能夠獲取instance) :return: """
        return instance.pk

    class Meta:
        model = models.User
        fields = ('name', 'alias', 'register_date', 'id')
        
class User(views.APIView):
    def get(self, request):
        qs = models.User.objects.all()
        serializer = UserSerializer(qs, many=True)
        return response.Response({ 
 
   
            'status': 0,
            'data': serializer.data
        })

在這裏插入圖片描述

2.4 字段加工

通常狀況下,網站的圖片使用CDN加速,CDN可能來自不一樣的CDN服務提供商。若是要換CDN服務提供商,那麼接口返回的全部圖片連接都須要更新。若是咱們對連接部分進行處理,讓連接的域名部分能夠獨立出來,這樣若是換提供商,直接修改域名就能夠了,下面是個簡單的示例:

class ImagePathField(serializers.Field):
    def __init__(self, domain=None, *args, **kwargs):
        self.domain = domain
        super(ImagePathField, self).__init__(*args, **kwargs)

    def to_representation(self, value):
        if not self.domain:
            self.domain = 'www.aistudies.com.cn'
        # return '{}' + '{}.png'.format(self.domain, value)
        return f'{self.domain}/{value}.jpg'

    def to_internal_value(self, data):
        return data

class UserSerializer(serializers.ModelSerializer):
    name = serializers.CharField(max_length=5, error_messages={ 
 
   'max_length': '字段太長!'})
    alias = serializers.CharField(source='name')
    register_date = serializers.DateTimeField(format='%Y-%m-%d %X')
    images = serializers.CharField()
    # 若是source=None,則返回給客戶端的img是null
    img = ImagePathField(domain='www.baidu.com', source='images')

    def get_id(self, instance):
        """ :param instance: 查詢的結果(直接寫上instance就能夠獲取instance) :return: """
        return instance.pk

    class Meta:
        model = models.User
        fields = ('name', 'alias', 'register_date', 'id', 'images', 'img')

class User(views.APIView):
    def get(self, request):
        qs = models.User.objects.all()
        serializer = UserSerializer(qs, many=True)
        return response.Response({ 
 
   
            'status': 0,
            'data': serializer.data
        })

在這裏插入圖片描述
只是做爲例子使用了下,固然這裏仍是有不少問題的!

3. DRF認證
3.1 自定義認證

有些API不須要用戶登陸就能夠訪問,可是有些須要用戶登陸才能夠訪問。Django REST framework中內置認證組件,能夠實現須要用戶登陸才能夠訪問API的功能。藉助內置認證組件,能夠方便地自定義認證規則:

models.py

from django.db import models

# Create your models here.
class User(models.Model):
    name = models.CharField(max_length=32, unique=True)
    pwd = models.CharField(max_length=64)
    user_type_choices = (
        (1, '普通用戶'),
        (2, 'VIP'),
        (3, 'SVIP')
    )
    user_type = models.IntegerField(choices=user_type_choices)

class UserToken(models.Model):
    token = models.CharField(max_length=64)
    user = models.OneToOneField(to='User',on_delete=models.CASCADE)

在這裏插入圖片描述
寫一些數據,用於登陸認證測試:
在這裏插入圖片描述
setting.py

DATABASES = { 
 
   
    'default': { 
 
   
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'drftest',
        'USER': 'root',
        'PASSWORD': '123456',
        'HOST': 'localhost',
        'PORT': '3306'
    },
    'mysql': { 
 
   
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

views.py:

import hashlib, time
from rest_framework.views import APIView, exceptions
from .models import User, UserToken
from django.http import JsonResponse

# Create your views here.
def md5(name):
    obj = hashlib.md5(bytes(name, encoding='utf-8'))
    ctime = str(time.time())
    obj.update(bytes(ctime, encoding='utf-8'))
    return obj.hexdigest()

class Authenticate:
    def authenticate(self, request):
        token = request._request.GET.get('token')
        print(token)
        token_obj = UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用戶認證失敗!')
        # 在rest framework內部會將整個兩個字段賦值給request,共後續操做使用
        # 有三種返回值,None:表示無論;異常:沒有經過認證;元組:返回下面兩個元素,一個給request.use,一個給request.auth
        return (token_obj.user, token_obj)  # (request.user,request.auth)

    def authenticate_header(self, request):
        pass

class AuthView(APIView):
    def post(self, request, *args, **kwargs):
        """ 用戶登陸成功後,返回根據時間輟生成的token,每次登陸的時間不一樣,每次生成的token也不一樣,都被記錄到token表中用於與每次請求帶着的token進行對比。若是對比成功,則認證成功,是容許訪問的。 :param request: :param args: :param kwargs: :return: """
        ret = { 
 
   'code': 1000, 'msg': None}
        try:
            # 須要以form-data的方式提交
            name = request._request.POST.get('name')
            pwd = request._request.POST.get('pwd')
            instance = User.objects.filter(name=name, pwd=pwd).first()  # User object (1),
            print(type(instance))  # <class 'app.models.User'>,加不加all()結果同樣
            print(instance)  # User object (1),加不加all()結果同樣
            if not instance:
                ret['code'] = 1001
                ret['msg'] = '用戶名或密碼錯誤'
            else:
                token = md5(name=name)
                UserToken.objects.update_or_create(user=instance, defaults={ 
 
   'token': token})
                ret['token'] = token
        except Exception as e:
            ret['code'] = 1001
            ret['msg'] = '請求異常'
        return JsonResponse(ret)

class OrderView(APIView):
    # 列表中有寫認證類則須要認證,使用自定義的Authenticate類來認證
    authentication_classes = [Authenticate, ]

    def get(self, request, *args, **kwargs):
        # request.user
        # request.auth
        self.dispatch
        order_dict = { 
 
   
            1: { 
 
   
                'name': "thanlon",
                'age': 24,
                'gender': '男',
            },
            2: { 
 
   
                'name': "kiku",
                'age': 26,
                'gender': '女',
            },
        }
        # token = request._request.GET.get('token')
        ret = { 
 
   'code': 1000, "msg": None, 'data': None}
        try:
            ret['data'] = order_dict
        except Exception as e:
            pass
        return JsonResponse(ret)

登陸時生成的token:
在這裏插入圖片描述
用於用戶登陸成功生成token的類AuthView不須要認證,OrderView類須要認證,若是不帶token訪問這個接口會返回失敗的認證:
在這裏插入圖片描述
帶着這token來訪問這個接口,注意這裏從url中獲取的,要把token放在url上,不要放到請求頭髮送過去。結果發現能夠訪問到請求的數據:
在這裏插入圖片描述
也能夠放到請求頭中發過去,認證類獲取token的時候要到請求頭中獲取。

3.2 認證流程

想要更好地使用認證組件,不得不研究和學習下認證組件的實現原理:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
認證能夠加多個,通常不會使用到多個認證。列表中的認證類中從第一個開始,若是第一個認證沒有作處理,返回None,則交給下一個認證處理:

import hashlib, time
from rest_framework.views import APIView, exceptions
from .models import User, UserToken
from django.http import JsonResponse

# Create your views here.
def md5(name):
    obj = hashlib.md5(bytes(name, encoding='utf-8'))
    ctime = str(time.time())
    obj.update(bytes(ctime, encoding='utf-8'))
    return obj.hexdigest()

class Authenticate:
    def authenticate(self, request):
        token = request._request.GET.get('token')
        print(token)
        token_obj = UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用戶認證失敗!')
        # 在rest framework內部會將整個兩個字段賦值給request,共後續操做使用
        return (token_obj.user, token_obj)  # (request.name,request.auth)

    def authenticate_header(self, request):
        pass

class FirstAuthenticate:
    def authenticate(self, request):
        pass

    def authenticate_header(self, request):
        pass

class AuthView(APIView):
    def post(self, request, *args, **kwargs):
        print(md5('thanlon'))
        ret = { 
 
   'code': 1000, 'msg': None}
        try:
            # 須要以form-data的方式提交
            name = request._request.POST.get('name')
            pwd = request._request.POST.get('pwd')
            instance = User.objects.filter(name=name, pwd=pwd).first()  # User object (1),
            print(type(instance))  # <class 'app.models.User'>,加不加all()結果同樣
            print(instance)  # User object (1),加不加all()結果同樣
            if not instance:
                ret['code'] = 1001
                ret['msg'] = '用戶名或密碼錯誤'
            else:
                token = md5(name=name)
                UserToken.objects.update_or_create(user=instance, defaults={ 
 
   'token': token})
                ret['token'] = token
        except Exception as e:
            ret['code'] = 1001
            ret['msg'] = '請求異常'
        return JsonResponse(ret)

class OrderView(APIView):
    # 須要認證,使用自定義的Authenticate類來認證
    authentication_classes = [FirstAuthenticate, Authenticate, ]

    def get(self, request, *args, **kwargs):
        # request.name
        # request.auth
        self.dispatch
        order_dict = { 
 
   
            1: { 
 
   
                'name': "thanlon",
                'age': 24,
                'gender': '男',
            },
            2: { 
 
   
                'name': "kiku",
                'age': 26,
                'gender': '女',
            },
        }
        # token = request._request.GET.get('token')
        ret = { 
 
   'code': 1000, "msg": None, 'data': None}
        try:
            ret['data'] = order_dict
        except Exception as e:
            pass
        return JsonResponse(ret)

流程概述:

- dispatch
	- 封裝request
		- 獲取定義的認證類(全局或者局部),經過列表生成式建立對象
	- initial
		- perform_authentication
			request.user
				內部循環認證類執行authenticate方法
3.3 全局配置認證

若是咱們不使用本身的認證類,默認使用Django REST framework的認證類,路徑在配置文件中。源碼中有體現:
在這裏插入圖片描述
在這裏插入圖片描述
加下來能夠這樣來配置全局的認證類。在app目錄下建立用於存放認證類的 auth,py 文件(認證的類不要寫在views中,不然可能引用出現問題),而後在 settings.py 的認證配置項指向這個文件:

settings.py:

REST_FRAMEWORK = { 
 
   
    'DEFAULT_AUTHENTICATION_CLASSES': ['app.auth.FirstAuthenticate', 'app.auth.Authenticate', ]
}

auth.py:

from rest_framework.views import exceptions
from .models import UserToken

class FirstAuthenticate:
    def authenticate(self, request):
        pass

    def authenticate_header(self, request):
        pass


class Authenticate:
    def authenticate(self, request):
        token = request._request.GET.get('token')
        print(token)
        token_obj = UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用戶認證失敗!')
        # 在rest framework內部會將整個兩個字段賦值給request,共後續操做使用
        return (token_obj.user, token_obj)  # (request.name,request.auth)

    def authenticate_header(self, request):
        pass

這樣配置以後所有請求的方法都須要認證,可是有些是須要認證的,好比登陸的方法,也須要認證:
在這裏插入圖片描述
只須要在類中把authentication_classes設置爲空列表便可:

class AuthView(APIView):
    authentication_classes = []

    def post(self, request, *args, **kwargs):
        print(md5('thanlon'))
        ret = { 
 
   'code': 1000, 'msg': None}
        try:
            # 須要以form-data的方式提交
            name = request._request.POST.get('name')
            pwd = request._request.POST.get('pwd')
            instance = User.objects.filter(name=name, pwd=pwd).first()  # User object (1),
            print(type(instance))  # <class 'app.models.User'>,加不加all()結果同樣
            print(instance)  # User object (1),加不加all()結果同樣
            if not instance:
                ret['code'] = 1001
                ret['msg'] = '用戶名或密碼錯誤'
            else:
                token = md5(name=name)
                UserToken.objects.update_or_create(user=instance, defaults={ 
 
   'token': token})
                ret['token'] = token
        except Exception as e:
            ret['code'] = 1001
            ret['msg'] = '請求異常'
        return JsonResponse(ret)

正常訪問,不須要認證:
在這裏插入圖片描述

3.4 匿名用戶配置

能夠根據源碼,來配置匿名用戶:
在這裏插入圖片描述
在這裏插入圖片描述
settings.py:

REST_FRAMEWORK = { 
 
   
    # 'DEFAULT_AUTHENTICATION_CLASSES': ['app.auth.FirstAuthenticate', 'app.auth.Authenticate', ]
    'DEFAULT_AUTHENTICATION_CLASSES': ['app.auth.FirstAuthenticate', ],  # FirstAuthenticate中什麼也沒有作
    # 'UNAUTHENTICATED_USER': lambda x: '匿名用戶',
    'UNAUTHENTICATED_USER': None,  # request.user = None,默認是AnonymousUser
    'UNAUTHENTICATED_TOKEN': None  # request.auth = None
}
3.5 內置基本認證

Django REST framework中內置了不少內部認證類,
在這裏插入圖片描述
導入這些認證類的方式:

from rest_framework.authentication import BaseAuthentication,BasicAuthentication, SessionAuthentication,TokenAuthentication, RemoteUserAuthentication

BaseAuthentication類有兩種方法,全部的認證類必須繼承這個類:

class BaseAuthentication:
    """ All authentication classes should extend BaseAuthentication. """

    def authenticate(self, request):
        """ Authenticate the request and return a two-tuple of (user, token). 自定義認證操做的方法 """
        raise NotImplementedError(".authenticate() must be overridden.")

    def authenticate_header(self, request):
        """ Return a string to be used as the value of the `WWW-Authenticate` header in a `401 Unauthenticated` response, or `None` if the authentication scheme should return `403 Permission Denied` responses. 認證失敗以後給瀏覽器返回的響應頭, """
        pass

自定義認證類的時候,必須繼承BaseAuthentication,其它的認證類中BasicAuthentication是瀏覽器對用戶名和密碼進行base64加密,

HTTP_AUTHORIZATION:basic base64(用戶名和密碼)

而後放到請求頭裏面發送給服務端。服務端接收數據後進行處理,以後作一系列的校驗:
在這裏插入圖片描述
剩下的認證則是基於Django的session和token等實現的認證。

4. DRF權限
4.1 權限的基本使用

對於不一樣的視圖應該有不一樣的訪問權限,下面是權限的基本使用:

permission.py:

class MyPermission1:
    def has_permission(self, request, view):
        # 超級用戶能夠訪問
        if request.user.user_type != 3:
            return False
        return True


class MyPermission2:
    def has_permission(self, request, view):
        # 普通用戶能夠訪問,超級用戶不能夠訪問
        if request.user.user_type == 3:
            return False
        return True

views.py:

from rest_framework.views import APIView
from .models import User, UserToken
from django.http import JsonResponse
from utils.md5 import md5
from django.http import HttpResponse
from app import permission

class AuthView(APIView):
    authentication_classes = []

    def post(self, request, *args, **kwargs):
        ret = { 
 
   'code': 1000, 'msg': None}
        try:
            # 須要以form-data的方式提交
            name = request._request.POST.get('name')
            pwd = request._request.POST.get('pwd')
            instance = User.objects.filter(name=name, pwd=pwd).first()  # User object (1),
            print(type(instance))  # <class 'app.models.User'>,加不加all()結果同樣
            print(instance)  # User object (1),加不加all()結果同樣
            if not instance:
                ret['code'] = 1001
                ret['msg'] = '用戶名或密碼錯誤'
            else:
                token = md5(name=name)
                UserToken.objects.update_or_create(user=instance, defaults={ 
 
   'token': token})
                ret['token'] = token
        except Exception as e:
            ret['code'] = 1001
            ret['msg'] = '請求異常'
        return JsonResponse(ret)

class OrderView(APIView):
    # 須要認證,使用自定義的Authenticate類來認證,已經在全局中作了認證
    # authentication_classes = [FirstAuthenticate, Authenticate, ]
    permission_classes = [permission.MyPermission2, ]

    def get(self, request, *args, **kwargs):
        # request.user
        # request.auth
        print(request.user)  # User object (1)
        print(request.auth)  # print(request.auth)#User object (1)
        """ 權限: if request.user.user_type != 3: return HttpResponse('無權訪問') """
        self.dispatch
        order_dict = { 
 
   
            1: { 
 
   
                'name': "thanlon",
                'age': 24,
                'gender': '男',
            },
            2: { 
 
   
                'name': "kiku",
                'age': 26,
                'gender': '女',
            },
        }
        # token = request._request.GET.get('token')
        ret = { 
 
   'code': 1000, "msg": None, 'data': None}
        try:
            ret['data'] = order_dict
        except Exception as e:
            pass
        return JsonResponse(ret)

使用權限MyPermission1,普通用訪問拒絕:
在這裏插入圖片描述
使用MyPermission2,普通用戶正常訪問:
在這裏插入圖片描述

4.2 權限源碼流程

經過源碼熟悉權限的流程:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
能夠在自定的權限類中自定義權限拒絕的message:

class MyPermission1:
    message = '必須是超級用戶才能夠訪問'
    def has_permission(self, request, view):
        # 超級用戶能夠訪問
        if request.user.user_type != 3:
            return False
        return True

class MyPermission2:
    def has_permission(self, request, view):
        # 普通用戶能夠訪問,超級用戶不能夠訪問
        if request.user.user_type == 3:
            return False
        return True

在這裏插入圖片描述

4.3 全局權限配置

根據權限的流程,能夠對權限進行全局的配置:

settings.py:

REST_FRAMEWORK = { 
 
   
    'DEFAULT_PERMISSION_CLASSES': ['app.permission.MyPermission1', ]  # 全部的視圖對應的方法都被加上這樣的權限
}

permission.py:

class MyPermission1:
    message = '必須是超級用戶才能夠訪問'
    def has_permission(self, request, view):
        # 超級用戶能夠訪問
        if request.user.user_type != 3:
            return False
        return True

class MyPermission2:
    def has_permission(self, request, view):
        # 普通用戶能夠訪問,超級用戶不能夠訪問
        if request.user.user_type == 3:
            return False
        return True

源碼流程概述:

1. self.dispatch
2. def dispatch(self, request, *args, **kwargs)
3. self.initial(request, *args, **kwargs)
4. self.check_permissions(request)
5. def get_permissions(self)
6. permission.has_permission(request, self)
4.4 內置權限類

Django REST framework內置了一些權限類:
在這裏插入圖片描述
按照代碼規範,咱們本身寫的權限類應該繼承這個BasePermission權限類:

from rest_framework.permissions import BasePermission

class MyPermission1(BasePermission):
    def has_permission(self, request, view):
        return True

class MyPermission2(BasePermission):
    def has_permission(self, request, view):
     	return True

這裏的權限類基本上都是基於Django來作的,咱們通常不使用這些類,而是本身定製。

5. 訪問頻率控制/節流
5.1 訪問頻率控制基本實現

能夠根據 ip地址 來對用戶訪問頻率進行限制,因此咱們能夠自定這樣的訪問頻率控制的類:

還能夠經過具備惟一標識意義的用戶名或者用戶ID等!

throttle.py:

import time

VISIT_RECORD = { 
 
   }


class VisitThrottle:
    def allow_request(self, request, view):
        """ 設定10s以內只能訪問10次 :param request: :param view: :return: True or False 返回值爲True表示能夠訪問;返回值爲False或None表示訪問頻率過高被限制 """
        # 獲取用戶的ip地址,當前request(封裝)中有的就取當前的request,若是沒有就到_request中取
        remote_addr = request._request.META.get('REMOTE_ADDR')
        print(remote_addr)  # 127.0.0.1
        ctime = time.time()
        if remote_addr not in VISIT_RECORD:
            VISIT_RECORD[remote_addr] = [ctime]
            return True
        history = VISIT_RECORD.get(remote_addr)
        while history and history[-1] < ctime - 10:
            history.pop(-1)
        if len(history) < 10:
            history.insert(0, ctime)
            return True
        return False  # 寫不寫均可以,若是執行到這裏說明不可訪問的,返回False或None均可以表示不能夠訪問

    def wait(self, *args, **kwargs):
        pass

views.py:

from rest_framework.views import APIView
from .models import User, UserToken
from django.http import JsonResponse
from utils.md5 import md5
from app.throttle import VisitThrottle


class AuthView(APIView):
    authentication_classes = []
    permission_classes = []
    throttle_classes = [VisitThrottle, ]

    def post(self, request, *args, **kwargs):
        ret = { 
 
   'code': 1000, 'msg': None}
        try:
            # 須要以form-data的方式提交
            name = request._request.POST.get('name')
            pwd = request._request.POST.get('pwd')
            instance = User.objects.filter(name=name, pwd=pwd).first()  # User object (1),
            print(type(instance))  # <class 'app.models.User'>,加不加all()結果同樣
            print(instance)  # User object (1),加不加all()結果同樣
            if not instance:
                ret['code'] = 1001
                ret['msg'] = '用戶名或密碼錯誤'
            else:
                token = md5(name=name)
                UserToken.objects.update_or_create(user=instance, defaults={ 
 
   'token': token})
                ret['token'] = token
        except Exception as e:
            ret['code'] = 1001
            ret['msg'] = '請求異常'
        return JsonResponse(ret)

10s內只能發送10次請求,超出10次請求,則會返回。請求被限制:
在這裏插入圖片描述
當請求被拒絕後,系統還能夠返回 距離下次能夠請求的時間,能夠經過wait方法來設置:

throttle.py:

import time
# 能夠放到緩存中,默認Django REST framework中就是放在緩存中
VISIT_RECORD = { 
 
   }


class VisitThrottle:
    def __init__(self):
        self.history = None
        self.ctime = None

    def allow_request(self, request, view):
        """ 設定10s以內只能訪問10次 :param request: :param view: :return: True or False 返回值爲True表示能夠訪問;返回值爲False或None表示訪問頻率過高被限制 """
        # 獲取用戶的ip地址,當前request(封裝)中有的就取當前的request,若是沒有就到_request中取
        remote_addr = request._request.META.get('REMOTE_ADDR')
        print(remote_addr)  # 127.0.0.1
        self.ctime = time.time()  # 1593151531.1494734
        print(self.ctime)
        if remote_addr not in VISIT_RECORD:
            VISIT_RECORD[remote_addr] = [self.ctime]
            return True
        self.history = VISIT_RECORD.get(remote_addr)
        while self.history and self.history[-1] < self.ctime - 10:
            self.history.pop(-1)
        if len(self.history) < 10:
            self.history.insert(0, self.ctime)
            return True
        return False  # 寫不寫均可以,若是執行到這裏說明不可訪問的,返回False或None均可以表示不能夠訪問

    def wait(self, *args, **kwargs):
        """ 設置距離下次能夠請求的時間 :param args: :param kwargs: :return:默認返回None,表示使用默認 """
        """ 當請求被拒絕,會執行wait方法 """
        ctime = time.time()
        print(ctime)
        print(self.history[-1])
        """ return False { "detail": "Request was throttled. Expected available in 0 seconds." } """
        return 60 - (ctime - self.history[-1])

在這裏插入圖片描述

5.2 訪問頻率控制源碼流程

請求過來先走dispatch方法:
在這裏插入圖片描述
執行當前self.initialize_request方法:
在這裏插入圖片描述
執行self.check_throttles方法:
在這裏插入圖片描述
執行self.get_throttles方法,遍歷訪問控制類對象列表:
在這裏插入圖片描述
經過列表生成式生成訪問控制類對象列表:
在這裏插入圖片描述
默認的訪問控制類是從配置文件中獲取的,也是全局的訪問控制類:
在這裏插入圖片描述
在這裏插入圖片描述
若是訪問被拒絕,即執行allow_request方法返回False或者None,則執行throttle.wait()方法:
在這裏插入圖片描述
在這裏插入圖片描述

5.3 訪問頻率全局配置

同認證和權限同樣在配置文件中進行配置:

settings.py:

REST_FRAMEWORK = { 
 
   
    'DEFAULT_THROTTLE_CLASSES': ['app.throttle.VisitThrottle',]
}

一樣某些請求能夠不使用訪問頻率控制:

class AuthView(APIView):
    authentication_classes = []
    permission_classes = []
    # 訪問頻率控制
    throttle_classes = []

    def post(self, request, *args, **kwargs):
        pass
5.4 內置訪問頻率控制類

Django REST framewor中內置了五個訪問頻率控制類,全部的訪問權限控制類應該繼承BaseThrottle並重寫其中的allow_request(self, request, view)方法:
在這裏插入圖片描述
在這裏插入圖片描述
內置的訪問控制類SimpleRateThrottle其實幫助咱們實現了基於IP的訪問權限控制,咱們看它內部是怎麼實現的:
在這裏插入圖片描述
在這裏插入圖片描述
因此以前自定義的類實現的功能徹底能夠繼承SimpleRateThrottle來實現,首先自定義類繼承SimpleRateThrottle,並設置scope:

from rest_framework.throttling import SimpleRateThrottle

class VisitThrottle(SimpleRateThrottle):
    # scope被看成key使用的,根據它到配置文件中取值
    scope = 'erics'

在配置文件中添加DEFAULT_THROTTLE_RATES:

REST_FRAMEWORK = { 
 
   
    'DEFAULT_THROTTLE_RATES': { 
 
   
        # 每分鐘訪問3次
        'erics': '3/m'
    }
}

SimpleRateThrottle類中的邏輯是首先根據scope從配置文件中獲取訪問頻率配置 3/m
在這裏插入圖片描述
而後執行將獲取到的rate也就是scope傳入parse_rate函數進行解析:
在這裏插入圖片描述
在這裏插入圖片描述
__init__構造函數執行完成以後,執行allow_request方法:
在這裏插入圖片描述
獲取key,把ip看成key:

class VisitThrottle(SimpleRateThrottle):
    # scope被看成key使用的,根據它到配置文件中取值
    scope = 'erics'

    def get_cache_key(self, request, view):
        """獲取key"""
        return self.get_ident()

接下來到緩存獲取全部記錄:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
以前自定義的時間,wait方法也已經作好,咱們不須要去寫:
在這裏插入圖片描述
最終咱們只須要幾行代碼就能夠完成對匿名用戶的訪問頻率的控制:

from rest_framework.throttling import SimpleRateThrottle

class VisitThrottle(SimpleRateThrottle):
    # scope被看成key使用的,根據它到配置文件中取值
    scope = 'erics'

    def get_cache_key(self, request, view):
        """獲取key"""
        return self.get_ident(request)
REST_FRAMEWORK = { 
 
   
    'DEFAULT_THROTTLE_CLASSES': ['app.throttle.VisitThrottle', ],
    'DEFAULT_THROTTLE_RATES': { 
 
   
        # 每分鐘訪問5次,/右邊只要首字母是m就能夠了
        'erics': '5/m'
    }
}

匿名用戶每分鐘只能訪問5次:
在這裏插入圖片描述
上面是對匿名用戶的訪問登陸控制,也能夠對登陸用戶作訪問頻率控制。只須要根據用戶名來作頻率控制條件:

from rest_framework.throttling import SimpleRateThrottle

class UserVisitThrottle(SimpleRateThrottle):
    # scope被看成key使用的,根據它到配置文件中取值
    scope = 'user_erics'

    def get_cache_key(self, request, view):
        """獲取key"""
        print(request)  # <rest_framework.request.Request object at 0x7f155c6b86d0>
        # 用戶認證成功以後就會有request.user
        print(request.user)  # User object (1)
        print(request.user.name)  # thanlon
        return request.user.name
REST_FRAMEWORK = { 
 
   
    # 'DEFAULT_THROTTLE_CLASSES': ['app.throttle.VisitThrottle', ],
    # 登陸的用戶使用根據用戶名來作頻率限制,匿名用戶使用IP來作頻率限制。這裏全局配置了登陸用戶的,匿名用戶須要能夠單獨設置經過ip來控制
    'DEFAULT_THROTTLE_CLASSES': ['app.throttle.UserVisitThrottle', ],
    'DEFAULT_THROTTLE_RATES': { 
 
   
        # 每分鐘訪問5次,/右邊只要首字母是m就能夠了
        'erics': '5/m',
        'user_erics': '10/m'
    }
}

登陸的用戶每分鐘只能訪問10次:
在這裏插入圖片描述

6. DRF版本
6.1 GET傳參獲取版本

RESTful規範中規定版本能夠放到URL上,如 http://127.0.0.1:8000/api/users/?version=v2 經過GET進行傳參。自定義版本實現:

from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.versioning import BaseVersioning

class ParamVersion:
    def determine_version(self, request, *args, **kwargs):
        # version = request._request.GET.get('version')
        version = request.query_params.get('version')
        return version

class UsersView(APIView):
    versioning_class = ParamVersion  # request.version就會有值

    def get(self, request, *args, **kwargs):
        # request是Request的對象,若是封裝的request沒有回經過getattr方法到原生的Django的request中找
        # version = request._request.GET.get('version')
        # version = request.query_params.get('version')
        version = request.version
        # print(request.version)
        return HttpResponse('%s' % version)

若是使用內置的版本類,實際上咱們 不須要再自定義獲取版本的類,直接在視圖類方法中經過 request.version 獲取就能夠,由於內置已經幫咱們實現了。藉助內置類,咱們能夠少寫不少代碼:

from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.versioning import QueryParameterVersioning

class UsersView(APIView):
    versioning_class = QueryParameterVersioning

    def get(self, request, *args, **kwargs):
        version = request.version
        return HttpResponse('%s' % version)

能夠在配置文件中作默認的配置:

REST_FRAMEWORK = { 
 
   
    'DEFAULT_VERSION': 'v1',  # 不傳參或者version寫錯了則獲取到的版本號是v1
    'ALLOWED_VERSIONS': ['v1', 'v2'],  # 版本號必須是v1或者v2,不然報錯如:http://xxx/api/users/?version=v3
    'VERSION_PARAM': 'version',
}

在這裏插入圖片描述

6.2 URL路徑獲取版本

經過GET進行傳參用的相對少一些,比較多的是在URL路徑上傳參,即 ``。只須要修改URL,而後在視圖中引入 URLPathVersioning 內置版本類:

api/urls.py:

from django.urls import path, re_path
from . import views

urlpatterns = [
    # path('/users/', views.UsersView.as_view()),
    re_path('(?P<version>[v1,v2]+)/users/', views.UsersView.as_view()),
]

版本是一次性配置,能夠把版本的類加入到配置文件中:

settings.py:

REST_FRAMEWORK = { 
 
   
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',  # 使用URL路徑,下面的參數能夠不配置,只要URL正則匹配就能夠
    'DEFAULT_VERSION': 'v1',  # 不傳參或者version寫錯了則獲取到的版本號是v1
    'ALLOWED_VERSIONS': ['v1', 'v2'],  # 版本號必須是v1或者v2,不然報錯如:http://xxx/api/users/?version=v3
    'VERSION_PARAM': 'version',
}

GET傳參適用的內置類QueryParameterVersioning也能夠放到配置文件中,這樣下降了類和類之間的耦合度。

views.py:

from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView

class UsersView(APIView):

    def get(self, request, *args, **kwargs):
        version = request.version
        return HttpResponse('%s' % version)

在這裏插入圖片描述

6.3 內置版本類源碼流程

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

經過reserve反向生成url:

from django.urls import path, re_path
from . import views

urlpatterns = [
    # path('users/', views.UsersView.as_view()),
    re_path('(?P<version>[v1,v2]+)/users/', views.UsersView.as_view(), name='user'),
]
from django.shortcuts import HttpResponse
from rest_framework.views import APIView


class UsersView(APIView):

    def get(self, request, *args, **kwargs):
        # 獲取版本
        version = request.version
        # 獲取處理版本的對象
        versioning_scheme = request.versioning_scheme
        # 全部的版本類對象中有reverse方法用來反向生成url的,版本自動生成。不須要加上version參數,request=request表示在request自動設置上當前的版本
        url = request.versioning_scheme.reverse(viewname='user', request=request)
        self.dispatch
        return HttpResponse('%s' % (url,))

在這裏插入圖片描述
也能夠適用Django的反向生成url,只不過要手動加上版本參數:
在這裏插入圖片描述
在這裏插入圖片描述

6.4 內置版本類

Django REST framework內置了5個版本類,自定義版本類須要繼承BaseVersioning,可是通常咱們不須要自定義,內置的已經夠咱們使用的。QueryParameterVersioning是經過在URL中使用GET傳參;URLPathVersioning是URL路徑;HostNameVersioning是基於子域名作的;NamespaceVersioning是基於namespace,可是不經常使用;AcceptHeaderVersioning是基於請求頭。
在這裏插入圖片描述
放到cookie中也是能夠的,只要後端能正常接收。

7. DRF解析器
7.1 Django中數據的解析

Django接收請求和請求相關的數據時對請求頭和數據格式有必定的要求,首先看下對請求頭要求。只有請求頭中有 application/x-www-form-urlencoded,request.POST中才可能有值(去request.body中解析數據):

urls.py:

from django.urls import path, re_path
from . import views

urlpatterns = [
    re_path('(?P<version>[v1,v2]+)/django_request/', views.DjangoRequest.as_view()),
]

views.py:

from django.shortcuts import HttpResponse
from rest_framework.views import APIView

class DjangoRequest(APIView):
    def post(self, request, *args, **kwargs):
        from django.core.handlers.wsgi import WSGIRequest
        print(type(request._request))  # <class 'django.core.handlers.wsgi.WSGIRequest'>
        return HttpResponse()

能夠看下源碼中是如何作的:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
上面說過即使帶着這個Content-Type也不必定有值,數據解析(轉換)時對數據格式也是有要求的,數據格式須要是 ?name=thanlon&age=23 這種格式。

以後遇到request.POST沒有值,能夠考慮這兩個因素。

Form表單提交默認的Content-Type就是application/x-www-form-urlencoded,數據格式也是?name=thanlon&age=23。若是是Ajax提交:

$.ajax({ 
 
   
	url:...,
	type:POST,
	data:{ 
 
   'name':'thanlon','age':23}, // 內部也會轉換成?name=thanlon&age=23,再帶上Content-Type:application/x-www-form-urlencoded提交過去
})

Ajax能夠定製請求頭,這種狀況下request.body中有值,可是request.post中沒有值:

$.ajax({ 
 
   
	url:...,
	headers:{ 
 
   'Content-Type':'application/json'},
	type:POST,
	data:{ 
 
   'name':'thanlon','age':23},
})

Ajax將數據轉換爲JSON字符串傳到後臺,這種狀況下request.body中有值,可是request.post中仍是沒有值:

$.ajax({ 
 
   
	url:...,
	headers:{ 
 
   'Content-Type':'application/json'},
	type:post,
	data:JSON.stringfy({ 
 
   'name':'thanlon','age':23}), // {'name':'thanlon','age':23}
})

這種狀況下request.body中是有值,可使用 json.loads(request.body) 拿到json數據。固然,須要先把字節類型轉換爲字符串類型。

7.2 內置解析類的使用

經常使用的內置解析類有 JSONParser和FormParser,JSONParser類能夠解析請求頭中Content-Type是application/json的頭的數據,FormParser類能夠解析請求中Content-Type是application/x-www-form-urlencoded的頭的數據。Content-Type是application/json時:

urls.py:

from django.urls import re_path
from . import views

urlpatterns = [
    re_path('(?P<version>[v1,v2]+)/parser/', views.ParserView.as_view()),
]

views.py:

from django.shortcuts import HttpResponse
from rest_framework.views import APIView
from rest_framework.parsers import JSONParser

class ParserView(APIView):
    parser_classes = [JSONParser, ]

    def post(self, request, *args, **kwargs):
        """ 容許用戶發送JSON格式數據,請求頭中Content-Type是application/json,數據是{'name': 'erics'} :param request: :param args: :param kwargs: :return: """
        # request.data會觸發解析類進行數據的解析
        data = request.data
        print(type(data), data)  # <class 'dict'> {'name': 'erics'}
        return HttpResponse(f'{data}')

在這裏插入圖片描述
Content-Type是application/x-www-form-urlencoded時:

from django.shortcuts import HttpResponse
from rest_framework.views import APIView
from rest_framework.parsers import JSONParser, FormParser

class ParserView(APIView):
    parser_classes = [JSONParser, FormParser,]

    def post(self, request, *args, **kwargs):
        data = request.data
        """ 一、獲取用戶請求 二、獲取用戶請求體 三、獲取用戶請求頭,須要與parser_classes = [JSONParser, FormParser]中支持的請求頭進行比較 四、JSONParser、FormParser對象解析請求體 五、解析的結果放到request.data中 """
        print(type(data), data)  # <class 'django.http.request.QueryDict'> <QueryDict: {'name': ['erics']}>
        return HttpResponse(f'{data}')

在這裏插入圖片描述
Content-Type是multipart/form-data時,須要用到MultiPartParser解析類:
在這裏插入圖片描述
若是都不能夠解析請求的數據:
在這裏插入圖片描述

配置多個請求類時,請求頭中的Content-Type與哪個解析類匹配就使用哪一個解析。

全局配置只須要在配置文件中添加解析類的路徑便可:

REST_FRAMEWORK = { 
 
   
    'DEFAULT_PARSER_CLASSES':
        [
            'rest_framework.parsers.JSONParser',
            'rest_framework.parsers.FormParser',
            'rest_framework.parsers.MultiPartParser',
        ]
}
7.3 內置解析類

Django REST framework內置的解析類實際上有4種,都是根據請求頭中Content-Type值的不一樣來對數據作不一樣的解析:
在這裏插入圖片描述

7.4 解析源碼流程

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

8. DRF序列化
8.1 序列化基本使用

數據準備:

models.py:

from django.db import models


class UserInfo(models.Model):
    user_type_choices = (
        (1, '普通用戶'),
        (2, 'VIP用戶'),
        (3, 'SVIP用戶'),
    )
    user_type = models.IntegerField(choices=user_type_choices)
    name = models.CharField(max_length=32, unique=True)
    pwd = models.CharField(max_length=64)
    user_group = models.ForeignKey('UserGroup', on_delete=models.CASCADE)
    role = models.ManyToManyField('Role')


class Token(models.Model):
    user_info = models.OneToOneField(to='UserInfo', on_delete=models.CASCADE)
    token = models.CharField(max_length=64)


class UserGroup(models.Model):
    title = models.CharField(max_length=32)


class Role(models.Model):
    title = models.CharField(max_length=32)

生成的數據表:
在這裏插入圖片描述
在角色表中添加以下數據:
在這裏插入圖片描述
原先使用 json.dumps 方法對從數據庫中獲取到的數據進行序列化:

urls.py:

from django.urls import path, re_path
from . import views

urlpatterns = [
    re_path('(?P<version>[v1,v2]+)/roles/', views.RolesView.as_view()),
]

views.py:

from django.shortcuts import HttpResponse
from rest_framework.views import APIView
from . import models
import json

class RolesView(APIView):
    def get(self, request, *args, **kwargs):
        roles = models.Role.objects.all()
        print(roles)  # <QuerySet [<Role: Role object (1)>, <Role: Role object (2)>]>
        roles = models.Role.objects.all().values('id', 'title')
        print(roles)  # <QuerySet [{'id': 1, 'title': '管理員'}, {'id': 2, 'title': '超級管理員'}]>
        roles = list(roles)  # [<Role: Role object (1)>, <Role: Role object (2)>]
        print(roles)  # [{'id': 1, 'title': '管理員'}, {'id': 2, 'title': '超級管理員'}]
        ret = json.dumps(roles, ensure_ascii=False)
        return HttpResponse(ret)

在這裏插入圖片描述
如今咱們可使用Django REST framework中的序列化來對從數據庫中的獲取的數據進行序列化:

views.py:

from django.shortcuts import HttpResponse
from rest_framework.views import APIView
from . import models
import json
from rest_framework import serializers

class MySerializers(serializers.Serializer):
    """ 自定義序列化類 """
    id = serializers.IntegerField()
    title = serializers.CharField()

class RolesView(APIView):
    def get(self, request, *args, **kwargs):
        roles = models.Role.objects.all()
        # 由於對象不止一個,序列化[obj,obj,obj]這種,要使用many=True
        serializers = MySerializers(instance=roles, many=True)
        print(serializers)
        """ MySerializers(instance=<QuerySet [<Role: Role object (1)>, <Role: Role object (2)>]>, many=True): id = IntegerField() title = CharField() """
        # serializers.data是已經轉換完成的結果
        print(serializers.data) # [OrderedDict([('id', 1), ('title', '管理員')]), OrderedDict([('id', 2), ('title', '超級管理員')])]
        
        ret = json.dumps(serializers.data, ensure_ascii=False)
        return HttpResponse(ret)

在這裏插入圖片描述
對於從數據庫中獲取一條數據,若是適用序列化:

views.py:

from django.shortcuts import HttpResponse
from rest_framework.views import APIView
from . import models
import json
from rest_framework import serializers


class MySerializers(serializers.Serializer):
    """ 自定義序列化類 """
    id = serializers.IntegerField()
    title = serializers.CharField()


class RolesView(APIView):
    def get(self, request, *args, **kwargs):
        roles = models.Role.objects.first()
        # 由於就一個對象,序列化obj,要使用many=False
        serializers = MySerializers(instance=roles, many=False)  # 默認也是False
        print(serializers)
        """ MySerializers(instance=<Role: Role object (1)>): id = IntegerField() title = CharField() """
        print(serializers.data)  # {'id': 1, 'title': '管理員'}
        # serializers.data是已經轉換完成的結果
        ret = json.dumps(serializers.data, ensure_ascii=False)
        return HttpResponse(ret)

在這裏插入圖片描述

8.2 自定義字段

Django REST framework的序列化能夠容許咱們自定義字段,從數據庫中獲取字段值也特別方便:

from django.urls import path, re_path
from . import views

urlpatterns = [
    re_path('(?P<version>[v1,v2]+)/userinfo/', views.UserinfoView.as_view()),
]
from django.shortcuts import HttpResponse
from rest_framework.views import APIView
from . import models
import json
from rest_framework import serializers

class UserinfoSerializer(serializers.Serializer):
    username = serializers.CharField()
    pwd = serializers.CharField()
    user_type_id = serializers.CharField(source='user_type')  # source對應數據庫中字段,row.user_type
    user_type_title = serializers.CharField(source='get_user_type_display')  # row.get_user_type_display()
    group_title = serializers.CharField(source='user_group.title')
    rls = serializers.SerializerMethodField()  # 自定義展現

    def get_rls(self, row):
        row_obj_list = row.role.all()
        ret = []
        for item in row_obj_list:
            ret.append({ 
 
   'id': item.id, 'title': item.title})
        return ret


class UserinfoView(APIView):
    def get(self, request, *args, **kwargs):
        userinfo = models.UserInfo.objects.all()
        userinfo_serializer = UserinfoSerializer(userinfo, many=True)
        ret = json.dumps(userinfo_serializer.data, ensure_ascii=False)
        return HttpResponse(f"{ret}")

在這裏插入圖片描述

8.3 ModelSerializer

ModelSerializer類繼承了Serializer,內部作了一些操做能夠自動生成全部的字段:

class UserinfoSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = "__all__"

class UserinfoView(APIView):
    def get(self, request, *args, **kwargs):
        userinfo = models.UserInfo.objects.all()
        userinfo_serializer = UserinfoSerializer(userinfo, many=True)
        ret = json.dumps(userinfo_serializer.data, ensure_ascii=False)
        return HttpResponse(f"{ret}")

在這裏插入圖片描述
簡單的能夠直接用數據庫的字段適用ModelSerializer生成,複雜的能夠自定義:

class UserinfoSerializer(serializers.ModelSerializer):
    user_type_title = serializers.CharField(source='get_user_type_display')
    group_title = serializers.CharField(source='user_group.title')
    rls = serializers.SerializerMethodField()

    class Meta:
        model = models.UserInfo
        # fields = "__all__"
        fields = ['id', 'username', 'pwd', 'rls', 'user_type_title', 'group_title',]

    def get_rls(self, row):
        row_obj_list = row.role.all()
        ret = []
        for item in row_obj_list:
            ret.append({ 
 
   'id': item.id, 'title': item.title})
        return ret


class UserinfoView(APIView):
    def get(self, request, *args, **kwargs):
        userinfo = models.UserInfo.objects.all()
        userinfo_serializer = UserinfoSerializer(userinfo, many=True)
        ret = json.dumps(userinfo_serializer.data, ensure_ascii=False)
        return HttpResponse(f"{ret}")

在這裏插入圖片描述
生成字段的時候能夠額外加一些參數,能夠不用再定義group_title:

class UserinfoSerializer(serializers.ModelSerializer):
    user_type_title = serializers.CharField(source='get_user_type_display')
    # group_title = serializers.CharField(source='user_group.title')
    rls = serializers.SerializerMethodField()

    class Meta:
        model = models.UserInfo
        # fields = "__all__"
        fields = ['id', 'username', 'pwd', 'rls', 'user_type_title', ]
        extra_kwargs = { 
 
   'group_title': { 
 
   'source': 'user_group.title'}}  # 和上面自定義同樣

    def get_rls(self, row):
        row_obj_list = row.role.all()
        ret = []
        for item in row_obj_list:
            ret.append({ 
 
   'id': item.id, 'title': item.title})
        return ret

class UserinfoView(APIView):
    def get(self, request, *args, **kwargs):
        userinfo = models.UserInfo.objects.all()
        userinfo_serializer = UserinfoSerializer(userinfo, many=True)
        ret = json.dumps(userinfo_serializer.data, ensure_ascii=False)
        return HttpResponse(f"{ret}")

在這裏插入圖片描述

8.4 深度控制

只須要在Meta中設置 depth 字段就能夠 自動序列化連表 獲取關聯表中的字段:

class UserinfoSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = "__all__"
        """ depth:0~10,0表示只獲取UserInfo中的表中的字段的值 """
        depth = 1

class UserinfoView(APIView):
    def get(self, request, *args, **kwargs):
        userinfo = models.UserInfo.objects.all()
        userinfo_serializer = UserinfoSerializer(userinfo, many=True)
        ret = json.dumps(userinfo_serializer.data, ensure_ascii=False)
        return HttpResponse(f"{ret}")

在這裏插入圖片描述

class UserinfoSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = ['id', 'username', 'pwd', 'user_type', 'user_group', 'role']
        depth = 1

在這裏插入圖片描述

8.5 生成連接

Django REST framework中的序列化也能夠幫助咱們生成超連接:

urls.py:

from django.urls import path, re_path
from . import views

urlpatterns = [
    re_path('(?P<version>[v1,v2]+)/userinfo/', views.UserinfoView.as_view()),
    re_path('(?P<version>[v1,v2]+)/group/(?P<pk>\d+)', views.GroupView.as_view(), name='gp'),
]
from django.shortcuts import HttpResponse
from rest_framework.views import APIView
from . import models
import json
from rest_framework import serializers

class UserinfoSerializer(serializers.ModelSerializer):
    group = serializers.HyperlinkedIdentityField(view_name='gp', lookup_url_kwarg='pk',
                                                 lookup_field='user_group_id')  # pk和url上自定義的pk有關係

    class Meta:
        model = models.UserInfo
        fields = ['id', 'username', 'pwd', 'user_type', 'user_group', 'role', 'group']
        depth = 1

class UserinfoView(APIView):
    def get(self, request, *args, **kwargs):
        userinfo = models.UserInfo.objects.all()
        userinfo_serializer = UserinfoSerializer(userinfo, many=True, context={ 
 
   'request': request})
        ret = json.dumps(userinfo_serializer.data, ensure_ascii=False)
        return HttpResponse(f"{ret}")


class GroupSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserGroup
        fields = '__all__'


class GroupView(APIView):
    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk')  # url傳過來的pk對應/(?P<version>[v1,v2]+)/group/(?P<pk>\d+)/
        obj = models.UserGroup.objects.filter(pk=pk).first()
        group_serializer = GroupSerializer(instance=obj, many=False)
        ret = json.dumps(group_serializer.data, ensure_ascii=False)
        return HttpResponse(f"{ret}")

在這裏插入圖片描述
在這裏插入圖片描述

8.6 請求數據校驗

Django REST framework序列化還能夠作 請求數據校驗,簡單的數據校驗:

class UserGroupSerializer(serializers.Serializer):
    # title = serializers.CharField(error_messages={'required':'dsdsd'}) # error_messages暫時沒做用
    # 寫須要校驗的字段
    title = serializers.CharField()
    
    def validate_title(self, value):
        from rest_framework import exceptions
        raise exceptions.ValidationError('錯誤!')
        return value

class UserGroupView(APIView):
    def post(self, request, *args, **kwargs):
        print(request.data)  # {'title': ''} 
        ser = UserGroupSerializer(data=request.data)
        if not ser.is_valid():
            print(
                ser.errors['title'][0])  # {'title': [ErrorDetail(string='This field may not be blank.', code='blank')]}
        else:
            print(ser.validated_data,
                  type(ser.validated_data))  # OrderedDict([('title', '3組')]) <class 'collections.OrderedDict'>
            print(ser.validated_data['title'])  # 3組
        return HttpResponse()

在這裏插入圖片描述
在這裏插入圖片描述

8.7 自定義驗證規則

簡單的數據校驗通常不能知足需求,不少狀況下須要自定義驗證規則校驗數據:

class UserGroupValidator:
    def __init__(self, base):
        self.base = str(base)

    def __call__(self, value):
        """數據一提交過來就會執行這個__call__方法"""
        print('提交過來title的值', value)  # value是提交過來的值
        if not value.endswith(self.base):
            message = '標題必須以%s結尾!' % self.base
            raise serializers.ValidationError(message)

    def set_context(self, serializer_field):
        """執行驗證以前調用,serializer_field是當前字段對象,這裏用不到"""
        pass


class UserGroupSerializer(serializers.Serializer):
    title = serializers.CharField(validators=[UserGroupValidator('組'), ])


class UserGroupView(APIView):
    def post(self, request, *args, **kwargs):
        # print(request.data) # {'title': '3'}
        ser = UserGroupSerializer(data=request.data)
        if not ser.is_valid():
            print(
                ser.errors['title'][0])  # {'title': [ErrorDetail(string='This field may not be blank.', code='blank')]}
        else:
            print(ser.validated_data,
                  type(ser.validated_data))  # OrderedDict([('title', '3組')]) <class 'collections.OrderedDict'>
            print(ser.validated_data['title'])  # 3組
        return HttpResponse()

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
自定義驗證規則時,須要用到鉤子函數,須要從 is_valid 方法開始找。

9. DRF分頁
9.1 內置分頁

① 查看第 n 頁,每頁顯示 m 條數據。settings.py 中配置的是默認顯示的數據條數。使用到的內置類:PageNumberPagination

settings.py

REST_FRAMEWORK = { 
 
   
    'PAGE_SIZE': 2,
}

views.py:

from rest_framework import serializers
from api import models
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination


class PagerSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = "__all__"


class PagerView(APIView):
    def get(self, request, *args, **kwargs):
        # 獲取全部數據
        roles = models.Role.objects.all()
        # 建立分頁對象
        pg = PageNumberPagination()
        # 獲取數據庫中分頁的數據
        pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self)
        """ print(pager_roles) # [<Role: Role object (1)>, <Role: Role object (2)>] """
        # 序列化數據
        serializer = PagerSerializer(instance=pager_roles, many=True)
        """ ret = json.dumps(serializer.data) return HttpResponse(ret) """
        return Response(serializer.data)

在這裏插入圖片描述
在這裏插入圖片描述
② 第 n 頁向前向後查 m 條數據,使用到的內置類:LimitOffsetPagination

settings.py

REST_FRAMEWORK = { 
 
   
    'PAGE_SIZE': 2,
}
from rest_framework import serializers
from api import models
from rest_framework.response import Response
from rest_framework.pagination import LimitOffsetPagination


class PagerSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = "__all__"


class PagerView(APIView):
    def get(self, request, *args, **kwargs):
        # 獲取全部數據
        roles = models.Role.objects.all()
        # 建立分頁對象
        pg = LimitOffsetPagination()
        # 獲取數據庫中分頁的數據
        pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self)
        """ print(pager_roles) # [<Role: Role object (1)>, <Role: Role object (2)>] """
        # 序列化數據
        serializer = PagerSerializer(instance=pager_roles, many=True)
        """ ret = json.dumps(serializer.data) return HttpResponse(ret) """
        return Response(serializer.data)

在這裏插入圖片描述
在這裏插入圖片描述
③ 解決數據量比較大時,下降掃描的數據量。同時要對頁碼進行加密,防止因用戶隨意指定頁碼沒有實現下降掃描數據量而提升查詢效率的目的。使用到的內置類:CursorPagination這部分需不能直接使用內置累,須要自定義類來實現! 下面會有介紹。

9.2 自定義分頁

① 查看第 n 頁,每頁顯示 m 條數據

settings.py

REST_FRAMEWORK = { 
 
   
    'PAGE_SIZE': 2,
}

views.py:

from rest_framework import serializers
from api import models
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination


class MyPageNumberPagination(PageNumberPagination):
    page_size = 2  # 在這裏設置能夠覆蓋settings.py中的配置
    page_query_param = 'page'
    page_size_query_param = 'size'
    max_page_size = 3


class PagerSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = "__all__"


class PagerView(APIView):
    def get(self, request, *args, **kwargs):
        # 獲取全部數據
        roles = models.Role.objects.all()
        # 建立分頁對象
        pg = MyPageNumberPagination()
        # 獲取數據庫中分頁的數據
        pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self)
        """ print(pager_roles) # [<Role: Role object (1)>, <Role: Role object (2)>] """
        # 序列化數據
        serializer = PagerSerializer(instance=pager_roles, many=True)
        """ ret = json.dumps(serializer.data) return HttpResponse(ret) return Response(serializer.data) """
        return Response(serializer.data)

在這裏插入圖片描述

② 第 n 頁向前向後查 m 條數據

settings.py

REST_FRAMEWORK = { 
 
   
    'PAGE_SIZE': 2,
}

views.py:

from rest_framework import serializers
from api import models
from rest_framework.response import Response
from rest_framework.pagination import LimitOffsetPagination

class MyLimitOffsetPagination(LimitOffsetPagination):
    limit_query_param = 'limit'
    offset_query_param = 'offset'
    max_limit = 2


class PagerSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = "__all__"


class PagerView(APIView):
    def get(self, request, *args, **kwargs):
        # 獲取全部數據
        roles = models.Role.objects.all()
        # 建立分頁對象
        pg = MyLimitOffsetPagination()
        # 獲取數據庫中分頁的數據
        pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self)
        """ print(pager_roles) # [<Role: Role object (1)>, <Role: Role object (2)>] """
        # 序列化數據
        serializer = PagerSerializer(instance=pager_roles, many=True)
        """ ret = json.dumps(serializer.data) return HttpResponse(ret) return Response(serializer.data) """
        return Response(serializer.data)

在這裏插入圖片描述
③ 下降掃描的數據量,同時對頁碼進行加密

settings.py

REST_FRAMEWORK = { 
 
   
    'PAGE_SIZE': 2,
}

views.py:

from rest_framework import serializers
from api import models
from rest_framework.response import Response
from rest_framework.pagination import CursorPagination


class MyCursorPagination(CursorPagination):
    cursor_query_param = 'cursor'
    page_size = 2
    ordering = 'id'  # 倒序是-id
    page_size_query_param = None
    max_page_size = None


class PagerSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = "__all__"


class PagerView(APIView):
    def get(self, request, *args, **kwargs):
        # 獲取全部數據
        roles = models.Role.objects.all()
        # 建立分頁對象
        pg = MyCursorPagination()
        # 獲取數據庫中分頁的數據
        pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self)
        """ print(pager_roles) # [<Role: Role object (1)>, <Role: Role object (2)>] """
        # 序列化數據
        serializer = PagerSerializer(instance=pager_roles, many=True)
        """ ret = json.dumps(serializer.data) return HttpResponse(ret) return Response(serializer.data) return Response(serializer.data) """
        return pg.get_paginated_response(serializer.data)

在這裏插入圖片描述
在這裏插入圖片描述

get_paginated_response方法能夠返回更多的內容!

10. DRF視圖
10.1 GenericAPIView

視圖繼承 GenericAPIViewGenericAPIView 繼承 APIViewAPIView 繼承
在這裏插入圖片描述
在這裏插入圖片描述
settings.py:

REST_FRAMEWORK = { 
 
   
    'PAGE_SIZE': 2,
}

urls.py:

from django.urls import path, re_path
from . import views

urlpatterns = [
    re_path('(?P<version>[v1,v2]+)/testview/', views.TestView.as_view()),
]

views.py:

from rest_framework import serializers
from rest_framework.generics import GenericAPIView
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response


class PagerSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = "__all__"


class TestView(GenericAPIView):
    queryset = models.Role.objects.all()
    pagination_class = PageNumberPagination
    serializer_class = PagerSerializer

    def get(self, request, *args, **kwargs):
        # 獲取數據
        roles = self.get_queryset()  # queryset: models.Role.objects.all()
        # 分頁
        pager_roles = self.paginate_queryset(roles)
        # 序列化
        serializer = self.get_serializer(instance=pager_roles, many=True)
        return Response(serializer.data)

在這裏插入圖片描述

通常用不到GenericAPIView!

10.2 GenericViewSet

視圖繼承 GenericViewSet,能夠根據請求方法的不一樣把請求分發到不一樣的方法,比原來都分發到 get 而後判斷有無 id 要好一些。 GenericViewSet 繼承 ViewSetMixinGenericAPIView
在這裏插入圖片描述
在這裏插入圖片描述

ViewSetMixin只是重寫了as_view方法!

settings.py:

REST_FRAMEWORK = { 
 
   
    'PAGE_SIZE': 2,
}

urls.py:

from django.urls import path, re_path
from . import views

urlpatterns = [
    re_path('(?P<version>[v1,v2]+)/testview/', views.TestView.as_view({ 
 
   'get': 'list'})),
]

views.py:

from rest_framework import serializers
from rest_framework.viewsets import GenericViewSet
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response


class PagerSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = "__all__"


class TestView(GenericViewSet):
    queryset = models.Role.objects.all()
    pagination_class = PageNumberPagination
    serializer_class = PagerSerializer

    def list(self, request, *args, **kwargs):
        # 獲取數據
        roles = self.get_queryset()  # queryset: models.Role.objects.all()
        # 分頁
        pager_roles = self.paginate_queryset(roles)
        # 序列化
        serializer = self.get_serializer(instance=pager_roles, many=True)
        return Response(serializer.data)

在這裏插入圖片描述

頁碼是從1開始的!

10.3 ModelViewSet

ModelViewSet 繼承6個類:
在這裏插入圖片描述
settings.py:

REST_FRAMEWORK = { 
 
   
    'PAGE_SIZE': 2,
}

urls.py:

from django.urls import path, re_path
from . import views

urlpatterns = [
    re_path('(?P<version>[v1,v2]+)/testview/', views.TestView.as_view({ 
 
   'get': 'list','post':'create'})),
    re_path('(?P<version>[v1,v2]+)/testview/(?P<pk>\d+)', views.TestView.as_view(
        { 
 
   'get':'retrieve','delete':'destroy','put':'update','patch':'partial_update'}
    )),
]

views.py:

from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from rest_framework.pagination import PageNumberPagination


class PagerSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = "__all__"


class TestView(ModelViewSet):
    queryset = models.Role.objects.all()
    pagination_class = PageNumberPagination
    serializer_class = PagerSerializer

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

10.4 視圖的使用

增刪改查:ModelViewSet

增刪:CreateModelMixinCreateModelMixin 還有 GenericViewSet

複雜邏輯: GenericViewSetAPIView

11. DRF路由
11.1 基本路由

urls.py:

urlpatterns = [
    # http://127.0.0.1:8000/api/v1/testview/?format=json
    re_path('(?P<version>[v1,v2]+)/testview/', views.TestView.as_view({ 
 
   'get': 'list','post':'create'})),
    # http://127.0.0.1:8000/api/v1/testview.json
    re_path('(?P<version>[v1,v2]+)/testview\.(?P<format>\w+)', views.TestView.as_view({ 
 
   'get': 'list','post':'create'})),
    # http://127.0.0.1:8000/api/v1/testview/1?format=json
    re_path('(?P<version>[v1,v2]+)/testview/(?P<pk>\d+)', views.TestView.as_view({ 
 
   'get':'retrieve','delete':'destroy','put':'update','patch':'partial_update'})),
    # http://127.0.0.1:8000/api/v1/testview/1.json
    re_path('(?P<version>[v1,v2]+)/testview/(?P<pk>\d+)\.(?P<format>\w+)', views.TestView.as_view({ 
 
   'get':'retrieve','delete':'destroy','put':'update','patch':'partial_update'})),
]

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

11.2 自動生成路由

Django REST framework渲染器能夠自動幫助咱們生成路由,查看生成的路由信息:

urls.py:

from django.urls import re_path, include
from . import views
from rest_framework import routers

router = routers.DefaultRouter()
router.register('x', views.TestView)
router.register('xx', views.TestView)
urlpatterns = [
    re_path('(?P<version>[v1,v2]+)/', include(router.urls))
]

views.py:

from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from rest_framework.pagination import PageNumberPagination


class PagerSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = "__all__"


class TestView(ModelViewSet):
    queryset = models.Role.objects.all()
    pagination_class = PageNumberPagination
    serializer_class = PagerSerializer

在這裏插入圖片描述

每個生成四個,分別是增刪改查!若是增刪改查都使用,則可使用自動生成!

在這裏插入圖片描述

12. DRF渲染器
12.1 JSONRenderer

直接顯示JSON字符串:

urls.py:

from django.urls import re_path, include
from . import views
from rest_framework import routers

router = routers.DefaultRouter()
router.register('x', views.TestView)
router.register('xx', views.TestView)
urlpatterns = [
    re_path('(?P<version>[v1,v2]+)/', include(router.urls))
]

settings.py:

REST_FRAMEWORK = { 
 
   
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ]
}

views.py:

from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from rest_framework.pagination import PageNumberPagination
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer

class PagerSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = "__all__"


class TestView(ModelViewSet):
    renderer_classes = [JSONRenderer]
    queryset = models.Role.objects.all()
    pagination_class = PageNumberPagination
    serializer_class = PagerSerializer

在這裏插入圖片描述

12.2 BrowsableAPIRenderer

顯示更好看的壓面:

views.py:

from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from rest_framework.pagination import PageNumberPagination
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer

class PagerSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = "__all__"


class TestView(ModelViewSet):
    renderer_classes = [JSONRenderer]
    queryset = models.Role.objects.all()
    pagination_class = PageNumberPagination
    serializer_class = PagerSerializer

在這裏插入圖片描述

12.3 AdminRenderer

views.py:

from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from rest_framework.pagination import PageNumberPagination
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer,AdminRenderer

class PagerSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = "__all__"


class TestView(ModelViewSet):
    renderer_classes = [JSONRenderer,BrowsableAPIRenderer,AdminRenderer]
    queryset = models.Role.objects.all()
    pagination_class = PageNumberPagination
    serializer_class = PagerSerializer

在這裏插入圖片描述

AdminRenderer通常不用!

本文同步分享在 博客「Erics-1996」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索