DRF之三大認證

Xiao0101發表於2024-05-02

一、認證

1、自定義認證

在前面說的 APIView 中封裝了三大認證,分別為認證、許可權、頻率。認證即登入認證,許可權表示該使用者是否有許可權訪問介面,頻率表示使用者指定時間內能訪問介面的次數。整個請求最開始的也是認證。

(1)需求

  • 登陸認證
  • 使用者登陸成功--》簽發token
  • 以後需要登陸才能訪問的介面,必須寫到當時登陸簽發的token過來
  • 後端驗證--》驗證透過--》繼續後續操作

(2)定義模型表

from django.contrib.auth.models import User
from django.db import models


class Book(models.Model):
    name = models.CharField(max_length=64)
    price = models.IntegerField()
    publish = models.CharField(max_length=64)

class UserInfo(AbstractUser):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    user_type=models.IntegerField(choices=((1,'註冊使用者'),(2,'普通管理員'),(3,'超級管理員')),default=1)

    @property
    def is_authenticated(self):
        return True

    class Meta:
        verbose_name_plural = '使用者表'


class UserToken(models.Model):
    # 用於存放uuid
    token = models.CharField(max_length=64)  # 存使用者登陸狀態【作用等同於session表】
    user = models.OneToOneField(to='UserInfo', on_delete=models.CASCADE)  # 跟User表是一對一

(3)登陸介面

import uuid
from rest_framework.viewsets import ViewSet
from django.contrib import auth

# 繼承 ViewSet,可以自動新增路由,也可以使用 action 裝飾器自定義請求方法
class LoginView(ViewSet):
	# 登入功能不需要認證,設為空列表
    authentication_classes = []
    permission_classes = []

    @action(methods=['post', ], detail=False)
    def login(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        # 驗證使用者名稱以及密碼是否正確
        user = auth.authenticate(request, username=username, password=password).first()
        if not user:
            return Response({'code': 10001, 'msg': '使用者名稱或密碼錯誤'})
        else:
            token = str(uuid.uuid4())
            # defaults 是需要更新的資料, user可以理解為是篩選的條件
            # UserToken.objects.filter(user=user).update('token':token)
            models.UserToken.objects.update_or_create(defaults={'token': token}, user=user)
            return Response({'code': 10000, 'msg': '登入成功', 'token': token})

(4) 認證類

  • 自定義認證表需要建立認證類,首先繼承擴充 BaseAuthentication

  • 匯入from rest_framework.authentication import BaseAuthentication

from rest_framework.authentication import BaseAuthentication
from .models import UserToken
from rest_framework.exceptions import AuthenticationFailed  # 異常
'''
1 寫個類,繼承BaseAuthentication
2 重寫 authentication 方法
3 在authentication 中完成使用者登陸的校驗
    -使用者攜帶token--》能查到,就是登陸了
    -使用者沒帶,或查不到,就是沒登入
4 如果校驗透過,返回當前登入使用者和token
5 如果沒校驗透過,拋AuthenticationFailed
'''
class LoginAuth(BaseAuthentication):
    # 重寫 BaseAuthentication 中的 authenticate 方法
    def authenticate(self, request):
        # request 是新的request,從request中取出使用者攜帶的token
        # 使用者帶在哪了? 後端定的:能放哪? 請求地址 ?token   請求體中  請求頭中
        # 放在請求頭中
        token=request.META.get('HTTP_TOKEN')
        # 校驗
        user_token=UserToken.objects.filter(token=token).first()
        if user_token:
            # 返回的第一個值是當前登入使用者,第二個值是 token
            user=user_token.user
            return user,token
        else:
            raise AuthenticationFailed('很抱歉,您沒有登陸,不能操作')

(5)使用

① 區域性使用

  • 區域性使用,只需要在檢視類里加入:
class BookView(GenericViewSet, ListModelMixin, CreateModelMixin, DestroyModelMixin):
    # 寫個認證類,在檢視類上配置即可
    authentication_classes = [LoginAuth]

② 全域性使用

  • 在配置檔案中新增配置,DEFAULT_AUTHENTICATION_CLASSES 的值為列表,其包含認證類路徑
REST_FRAMEWORK = {
    # 許可權認證
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'utils.authentication.LoginAuth'
    ],
}
  • 如果想區域性禁用,也可以實現
class UserView(ViewSet):
    authentication_classes = [] # 列表為空就行了,因為區域性優先

③ 選擇使用

  • 可以選擇某一些方法可以認證,在檢視類中新增 get_authenticators
def get_authenticators(self):
    if self.request.method != 'GET':
        return [LoginAuth(), ]

(6)總結流程

  • 繼承 BaseAuthentication
  • 重寫 authenticate 方法,方法中編寫邏輯,存在需要返回元組,為登入使用者和 token,不存在則報 AuthenticationFailed 認證異常
  • 編寫好認證類可以在區域性使用,也可以在全域性使用

2、內建認證類

  • SessionAuthentication 之前老的 session 認證登入方式用,後期不用
  • BasicAuthentication 基本認證方式
  • TokenAuthentication 使用 token 認證方式,也可以自己寫

可以在配置檔案中配置全域性預設的認證方案

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

也可以在每個檢視中透過設定authentication_classess屬性來設定

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

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

二、許可權

登入認證成功後,還需要認證許可權,有一些介面需要指定許可權才能訪問。所以許可權需要和登入認證相關聯。每個人的許可權在表中預設設為普通使用者。

注意:如果設定了許可權,那麼必需先透過使用者認證才能進行許可權校驗。

1、自定義許可權

  • 自定義許可權需要繼承 BasePermission 編寫許可權類
  • 匯入語句:from rest_framework.permissions import BasePermission
from rest_framework.permissions import BasePermission
from .models import UserToken, UserInfo

class UserPermission(BasePermission):
	# message 為認證失敗提示資訊
    message = ''

	# 需要重寫 has_permission 方法,原方法預設返回 True
    def has_permission(self, request, view):
    	# 獲取當前登入使用者
        user = request.user
        # 獲取當前使用者許可權型別
        user_type = user.user_type
        if user_type == 1:
        	# 許可權符合返回 True
            return True
        else:
        	# 許可權不符合,新增提示資訊,並返回 False
            # 只要使用了choice,可以透過get_欄位名_display()拿到數字對應的中文
            self.message = '你是: %s,許可權不夠' % user.get_user_type_display()
            return False

2、使用

(1)區域性使用

  • 區域性使用,只需要在檢視類里加入該許可權類即可:
permission_classes = [UserPermission,]

(2)全域性使用

  • 全域性使用也是在配置檔案中新增
REST_FRAMEWORK = {
    # 使用者認證
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'app01.authentication.LoginAuth'
    ],
    'DEFAULT_Permission_CLASSES': [
        'utils.permission.UserPermission'
    ],
}
  • 如果想區域性禁用,也可以實現
# 例如區域性禁用---》登入介面
class PublishView(GenericViewSet):
    permission_classes = []

(3)選擇使用

  • 在檢視類中新增 get_permissions 判斷如果請求方式符合就去認證
def get_permissions(self):
    if self.request.method == 'DELETE':
        return [UserPermission(), ]

3、總結流程

  • 繼承 BasePermission
  • 重寫 has_permission 方法,方法中編寫邏輯,有許可權返回 True,沒有許可權可以新增 message 並返回 False
  • 編寫好許可權類可以在區域性使用,也可以在全域性使用

4、內建許可權類

from rest_framework.permissions import AllowAny,IsAuthenticated,IsAdminUser,IsAuthenticatedOrReadOnly

-AllowAny 					允許所有使用者
-IsAdminUser  				校驗是不是 auth 的超級管理員許可權
-IsAuthenticated 			後面用,驗證使用者是否登入,登入後才有許可權,沒登入就沒有許可權
-IsAuthenticatedOrReadOnly 	瞭解即可

(1)區域性使用

  • 可以在具體的檢視中透過 permission_classes 屬性來設定,如下
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView

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

(2)全域性使用

  • 也可以在配置檔案中全域性設定預設的許可權管理類,如下
REST_FRAMEWORK = {
    ....
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ]
}
  • 如果未指明,則採用如下預設配置
'DEFAULT_PERMISSION_CLASSES': [
   'rest_framework.permissions.AllowAny',
]

三、 頻率

作用:限制介面的訪問頻率

1、 頻率類

from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
class CommonThrottle(SimpleRateThrottle):
    rate = '3/m'  # 一分鐘3次
    def get_cache_key(self, request, view):
        # 返回什麼,就會以什麼做限制--》ip地址限制;使用者id
        return request.META.get('REMOTE_ADDR')

2、使用

(1)區域性使用

在檢視類中新增

from .throttling import CommonThrottle
class PublishView(GenericViewSet):
    throttle_classes = [CommonThrottle]

(2)全域性使用

同樣,區域性禁用只要賦值空列表即可

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'app01.throttling.CommonThrottle'
    ],
}

3、總結流程

  • 繼承 SimpleRateThrottle 類

  • 重寫 get_cache_key 方法,該方法返回的值是限制的依據,例如按照 IP 地址來作為限制條件,設定每個 IP 地址訪問的次數。需要注意的是,IP 地址在 request.META 中獲取 'REMOTE_ADDR'

  • 接下來需要配置 setting ,需要設定訪問的頻率。首先需要設定屬性 scope,該屬性的值會作為頻率的鍵名,在 setting 配置檔案 REST_FRAMEWORK 中的 DEFAULT_THROTTLE_RATES 配置,鍵名是 scope,鍵值是字串,格式為 'x/y',x 表示訪問的次數,y 表示訪問的時間區間(可以為 s(秒)、m(份)、h(時)、d(天))

  • 編寫好頻率類可以在區域性使用,也可以在全域性使用

4、 內建頻率類

(1)AnonRateThrottle

AnonRateThrottle 內建頻率類的功能:對於登入使用者不限制次數,只未登入使用者限制次數,限制的次數需要在配置檔案中配置。使用也支援全域性和區域性。

  • setting.py
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '5/day',
    }
}

(2)UserRateThrottle

UserRateThrottle 內建頻率類的功能:限制登入使用者的頻率,限制的次數需要在配置檔案中配置。也支援全域性和區域性使用。

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.UserRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES': {
        'user': '1000/day',
    }
}

5、自定義頻率類

VISIT_RECORD = {}


class CommonThrottle(BaseThrottle):
    def __init__(self):
        self.history = None

    def allow_request(self, request, view):
        # 實現限流的邏輯
        # 以IP限流
        # 訪問列表 {IP: [time1, time2, time3]}
        # 1、獲取請求的IP地址
        ip = request.META.get("REMOTE_ADDR")
        # 2、判斷IP地址是否在訪問列表
        now = time.time()

        if ip not in VISIT_RECORD:
            # (1)不在 需要給訪問列表新增key,value
            VISIT_RECORD[ip] = [now, ]
            return True
            # (2)在 需要把這個IP的訪問記錄 把當前時間加入到列表
        history = VISIT_RECORD[ip]
        history.insert(0, now)

        # 3、確保列表裡最新訪問時間以及最老的訪問時間差 是1分鐘
        while history and history[0] - history[-1] > 60:
            history.pop()
        self.history = history
        # 4、得到列表長度,判斷是否是允許的次數
        if len(history) > 3:
            return False
        else:
            return True

    def wait(self):
        # 返回需要再等多久才能訪問
        if not self.history:
            return 0

        time = max(0, 60 - (self.history[0] - self.history[-1]))
        return time

在這段程式碼中,history 是一個記錄特定 IP 地址訪問時間戳的列表。這個列表儲存了特定 IP 地址在一分鐘內的訪問時間戳,用於限制該 IP 地址的訪問頻率。

在 allow_request 方法中,當一個請求到達時,會檢查該請求的 IP 地址是否已經存在於 VISIT_RECORD 中。如果存在,會將當前時間戳插入到該 IP 地址對應的歷史訪問時間戳列表中,並且清理超過一分鐘的舊時間戳。

在 wait 方法中,會計算當前時間與最新訪問時間戳的時間差,以確定需要等待多久才能再次允許該 IP 地址的訪問。如果 self.history 為空,表示該 IP 地址尚未有訪問記錄,因此返回等待時間為 0。

因此,self.history 在這段程式碼中用於跟蹤特定 IP 地址的訪問時間戳列表,以便在限制訪問頻率時進行判斷和計算。

6、其他頻率類

  • AnonRateThrottle
    • 限制所有匿名未認證使用者,使用 IP 區分使用者。
    • 使用 DEFAULT_THROTTLE_RATES[‘anon’] 來設定頻次
  • UserRateThrottle
    • 限制認證使用者,使用 User id 來區分。
    • 使用 DEFAULT_THROTTLE_RATES[‘user’] 來設定頻次
  • ScopedRateThrottle
    • 限制使用者對於每個檢視的訪問頻次,使用 ip 或 user id

四、排序

一般涉及到了查詢所有才有排序,對於表模型查詢所有資料時需要根據某個欄位進行排序時,我們就可以使用到REST framework提供的內建排序元件OrderingFilter來幫助我們快速指名資料按照指定欄位進行排序,遵循了restful規範中:位址列中帶過濾條件。

  • http://127.0.0.1:8008/app01/api/v1/books/?ordering=price 升序
  • http://127.0.0.1:8008/app01/api/v1/books/?ordering=price 倒序

1、使用步驟

from rest_framework.filters import OrderingFilter
# 在檢視類中配置
class BookListView(GenericViewSet,ListModelMixin):
    # 必須是繼承 GenericAPIView 的檢視類---》繼承APIView是不能這麼配置的
    filter_backends = [OrderingFilter]
    # 必須指定表模型資料欄位,可以寫多個排序欄位
    ordering_fields=['price','name']  # 預設是按id排序

# 訪問
http://127.0.0.1:8008/app01/api/v1/books1/?ordering=-price
http://127.0.0.1:8008/app01/api/v1/books1/?ordering=price
http://127.0.0.1:8008/app01/api/v1/books1/?ordering=-price,-name

# 定製返回格式--》重寫list--》重寫了list,過濾還生效嗎?
	-如果純自己寫了:不生了
    -如果還是使用父類的list方法:生
    -半自己寫:只要有他就會生效 self.filter_queryset()
    
	-filter_queryset(get_queryset())
	{code:100,msg:成功,results:[]}

注意:繼承APIView的是使用不了以上DRF提供的排序元件,需要自己寫,自己從請求地址中取出排序規則,然後自己排序

2、繼承APIView編寫排序

	from rest_framework.views import APIView
	from rest_framework.response import Response
	from rest_framework.viewsets import ViewSetMixin
	from rest_framework.mixins import ListModelMixin
	
	# 這裡沿用上面自動生成路由的方式,所以還是需要配置ViewSetMixin
	class BookView(ViewSetMixin, APIView, ListModelMixin):
		# 由於是APIView需要自己重寫list方法,在自動生成路由中是 {'get':'list'}
	    def list(self, request, *args, **kwargs):
	        # 從位址列中取出過濾條件
	        print(request.query_params)  # ?ordering=-price,id
	        query_params = request.query_params  # {ordering:price}
	        '''支援多個條件排序ordering=-price,id'''
	       
	        # http://127.0.0.1:8008/app01/api/v1/books/?ordering=price
	        if ',' in query_params.get('ordering'):
	            query = query_params.get('ordering').split(',')
	            book_list = models.Book.objects.all().order_by(*query)
	        else:
	            book_list = models.Book.objects.all().order_by(query_params.get('ordering'))
	        ser = BookSerializer(instance=book_list, many=True)
	        return Response(ser.data)

五、過濾

restful規範中,要求請求地址中帶過濾條件,五個介面中,只有查詢所有介面需要過濾和排序。其實排序也是過濾的一種,所以過濾跟排序並不衝突,是可以同時使用多個的。但是要注意一個大原則:把一次性過濾掉很多的往前放,因為我們猜測過濾的內部實現也就是for迴圈一個個過濾類,執行過濾類的filter_queryset方法。另外,實現過濾也有三種方式:

1、繼承GenericAPIView使用DRF內建過濾器實現過濾

首先匯入:from rest_framework.filters import SearchFilter

  • views.py
# 過濾和排序組合使用
http://127.0.0.1:8008/app01/api/v1/books1/?search=夢&ordering=price
# 只要出版社或名字帶海都能查出來---》search_fields=['name','publish']
http://127.0.0.1:8008/app01/api/v1/books1/?search=海 

# 使用方式
from rest_framework.filters import OrderingFilter,SearchFilter
class BookListView(GenericViewSet,ListModelMixin):
    filter_backends = [OrderingFilter,SearchFilter]
    ordering_fields = ['price', 'name']
    # 也可以多個欄位模糊匹配
    search_fields=['name','publish']

使用DRF內建過濾器,它搜尋結果的關係為or(或)的關係,不是and關係。並且只能是使用關鍵字search才能使用,如果我們想要用name=東&price=66這種方式的得使用別的方法來實現。

2、使用第三方模組django-filter實現and關係的過濾

  • 首先需要安裝第三方模組:
pip install django-filter
  • 然後再檢視類中配置
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.viewsets import ViewSetMixin
from rest_framework.generics import ListAPIView
# 使用這個第三方模組可以實現and關係,並且只能是精準匹配。並且暫不支援or關係
class BookView(ViewSetMixin,ListAPIView):
    queryset = models.Book.objects.all()
    serializer_class = BookSerializer

    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['name', 'price']
    
    # 但是對於django-filter來講它是支援擴寫的,所以是可以支援模糊匹配,具體操作自尋查詢
  • 訪問
# 查詢價格為66的圖書
http://127.0.0.1:8008/app01/api/v1/books1/?price=66

# 查詢價格為66的圖書 並且名字為 兩天 的圖書
http://127.0.0.1:8008/app01/api/v1/books1/?price=66&name=兩天  
  • 此外,django-filter還有更強大的搜尋,感興趣的可以深入瞭解一下

3、自定製過濾類

(1)使用步驟

  • 先定義一個過濾類,並且繼承BaseFilterBackend
  • 然後重寫filter_queryset方法,並且在內部完成過濾規則
  • 最後在檢視類中配置自定義的過濾類

(2)過濾類

from rest_framework.filters import BaseFilterBackend
from django.db.models import Q
class CommonFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        # 完成過濾,返回 qs物件
        # 查詢價格為66 或者 名字中包含海的資料
        # http://127.0.0.1:8008/app01/api/v1/books1/?price=66&name=海
        price = request.query_params.get('price', None)
        name = request.query_params.get('name', None)
        if price and name:
            queryset = queryset.filter(Q(price=price) | Q(name__contains=name))
        if price:
            queryset=queryset.filter(price=price)
        if name:
            queryset = queryset.filter(name__contains=name)
        return queryset

(3)檢視函式

from .filters import CommonFilter  
class BookView(ViewSetMixin,ListAPIView):
    queryset = models.Book.objects.all()
    serializer_class = BookSerializer
    # 無需再配置欄位了,因為在自定製類中已經寫好了過濾規則,所以在檢視類中無需配置
    filter_backends = [CommonFilter]
    # 這樣就實現了模糊匹配name欄位並且精準匹配price欄位,以及查詢單個欄位的規則

# 訪問:127.0.0.1:8000/api/v2/books/?name=遊記&price=666

4、排序搭配過濾使用

from .filters import CommonFilter  # 使用的是上面的自定製過濾類
from rest_framework.filters import OrderingFilter

class BookView(ViewSetMixin,ListAPIView):
    queryset = models.Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [OrderingFilter,CommonFilter]  # 排序加過濾,從左到右依次執行
    ordering_fields = ['price','id']

六、分頁

分頁也是隻針對查詢所有的介面,其他四個介面不需要分頁。drf內建了三個分頁器,對應三種分頁方式,內建的分頁類不能直接使用,需要繼承,定製一些引數後才能使用。一個介面只能有一種分頁方式,不能混合分頁方式。

另外,分頁跟過濾排序不衝突,都可以一起使用。分頁在web端、移動端都有涉及,後端一定會需要配合實現分頁介面。

使用步驟:

  • 寫個類,繼承某個分頁類
  • 在檢視類中配置:檢視類必須繼承 GenericAPIView

1、分頁器一:Pagination(基本分頁)

  • 透過自定義Pagination類,來為檢視新增不同分頁行為。在檢視中透過pagination_clas屬性來指明。
	from rest_framework.pagination import PageNumberPagination

	class CommonPageNumberPagination(PageNumberPagination):
	    page_size = 3 # 預設每頁顯示的條數
        # http://127.0.0.1:8008/app01/api/v1/books1/?page=2
	    page_query_param = 'page' # 使用該關鍵字指定頁碼(客戶端使用)
		# http://127.0.0.1:8008/app01/api/v1/books1/?page=2&size=3  查詢第2頁,每頁顯示3條
	    page_size_query_param = 'size' # 透過該關鍵字可以手動指定頁面顯示的資料條數(客戶端使用)
		# 每頁最多顯示10條
	    max_page_size = 5 # 只針對客戶端手動指定頁面顯示的資料條數做出一個限制,但不影響page_size屬性。

2、分頁器二:LimitOffsetPagination(偏移分頁)

  • 偏移分頁,該分頁元件作用是:從哪一條資料之後開始顯示,以及制定資料顯示的條數
  • 前端訪問形式:http://127.0.0.1:8080/book/?offset=2&limit=4,這表示從第3條資料之後顯示4條資料
from rest_framework.pagination import LimitOffsetPagination

class CommonLimitOffsetPagination(LimitOffsetPagination):
    # 等同於上面的:page_size
    default_limit = 2  # 如果前端沒有手動指定獲取的資料條數時,使用該屬性值
    limit_query_param = 'limit'  # 前端透過該關鍵字指定資料條數
    '每頁顯示條數,查詢的條數  例子 ?limit=100 意思就是每頁顯示100條,如果不傳應用default_limit的引數'
    offset_query_param = 'offset'  # 透過該關鍵字指定從哪條資料之後開始獲取資料
    '偏移量  舉個例子offset=6&limit=30  意思就是從第6條開始,拿30條'
    # 等同於上面的:max_page_size
    max_limit = 5  # 只限制limit_query_param能夠獲取的最大資料條數,而不影響default_limit
  • 從第二條資料之後開始指定獲取資料的條數,使用了limit指定了獲取6條,但是被max_limit=5給限制了,所以後端只能返回2條資料,如果不使用limit指定獲取的資料條數,那麼預設使用default_limit=2後端會返回2條資料。

3、分頁器三:CursorPagination(遊標分頁)

  • 使用遊標分頁的方式後就不能再其中使用排序了,因為其內部已經排好序了'
  • 並且只能選擇上一頁和下一頁,不能指定跳轉某一頁,但是速度快,針對與特別大的資料量,遊標分頁有優勢
from rest_framework.pagination import CursorPagination
class CommonCursorPagination(CursorPagination):
    cursor_query_param = 'cursor'  # 按遊標查詢的查詢條件,value值前端是不知道的,只能透過後臺返回
    page_size = 2  # 每頁顯示多少條啊
    ordering = 'id'  # 排序規則,必須是表中的欄位

4、檢視類

class BookListView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = []
    throttle_classes = []
    # pagination_class = CommonPageNumberPagination # 分頁方式只能選擇一種
    # pagination_class = CommonLimitOffsetPagination # 分頁方式只能選擇一種
    pagination_class = CommonCursorPagination # 分頁方式只能選擇一種

5、瞭解 基於APIView實現分頁功能


from rest_framework.viewsets import ViewSet
from .pagination import CommonPageNumberPagination,CommonLimitOffsetPagination
from .serializer import BookSerializer
from rest_framework.response import Response

class BookView(ViewSet):
    def list(self, reqeust):
        queryset = models.Book.objects.all()
        # 呼叫咱們寫的分頁類物件的paginate_queryset方法返回了,分頁後的queryset物件
        '''使用PageNumberPagination(普通分頁)'''
        # pagenation = CommonPageNumberPagination()  # 例項化得到物件
        '''使用LimitOffsetPagination(偏移分頁)'''
        # pagenation = CommonLimitOffsetPagination()  # 例項化得到物件
        '''使用CursorPagination(遊標分頁)'''
        pagenation = CommonCursorPagination()  # 例項化得到物件
        page = pagenation.paginate_queryset(queryset, reqeust, self)
        serializer = BookSerializer(page, many=True)  # page分頁後的物件
        '''使用它自己的方法(三種分頁方式都相容)'''
        # return pagenation.get_paginated_response(serializer.data)
        '''
	        使用定製的分頁返回格式,得去pagination原始碼裡面的對應每種分頁方法中的
	        def get_paginated_response(self, data):方法裡面看返回格式怎麼寫
	        '''
        '''也可以自己定製PageNumberPagination(普通分頁)'''
        return Response({
            'status': 100,
            'message': '查詢成功',
            'count': pagenation.page.paginator.count,
            'next': pagenation.get_next_link(),
            'previous': pagenation.get_previous_link(),
            'results': serializer.data,
        })
        '''定製LimitOffsetPagination'''
        return Response({
            'status': 100,
            'message': '查詢成功',
            'count': pagenation.count,
            'next': pagenation.get_next_link(),
            'previous': pagenation.get_previous_link(),
            'results': serializer.data,
        })

        '''定製CursorPagination'''
        return Response({
            'status': 100,
            'message': '查詢成功',
            'next': pagenation.get_next_link(),
            'previous': pagenation.get_previous_link(),
            'results': serializer.data,
        })

七、補充

1、斷言

  • 在python庫的原始碼中經常見到斷言,那麼怎麼理解斷言呢?
# 語法:
assert 條件,'字串'

# 翻譯成if
if 條件:
    條件成立,繼續走後續程式碼
else:
    raise Exception('字串')
  • 案例:
a = '1'
assert isinstance(a,int),'不行,a必須是int'

image

原始碼中使用:

assert isinstance(request, HttpRequest), (
        'The `request` argument must be an instance of '
        '`django.http.HttpRequest`, not `{}.{}`.'
        .format(request.__class__.__module__, request.__class__.__name__)
    )

2、模組與包

(1)什麼是模組?

  • 一個py 檔案,如果被匯入使用,它就是模組
  • 一個py檔案,點右鍵執行,它就叫 指令碼檔案

(2)什麼是包?

  • 一個資料夾下 有 init.py ,下面又有很多資料夾和py檔案,匯入使用的

(3)報錯資訊

ModuleNotFoundError: No module named 'xx' 
  • 但是這個模組有
  • 那麼我們就應該知道是匯入的問題:路徑不對

3、相對匯入和絕對匯入

在Python中,相對匯入和絕對匯入是用於匯入模組的兩種不同方式。它們的主要區別在於匯入模組時使用的路徑方式。

(1)絕對匯入

  • 絕對匯入是指從頂層包開始的完整匯入路徑,相對於環境變數 sys.path。在絕對匯入中,你需要指定完整的包路徑來匯入模組,從專案的根目錄開始一直到目標模組。絕對匯入可以透過使用絕對匯入語法來實現,例如:

    from package.subpackage import module
    
    # 例如
    ['D:\\PyCharm 2023.2.1\\workspace\\drf05', 'D:\\PyCharm 2023.2.1\\workspace\\drf05', 'D:\\PyCharm 2023.2.1\\plugins\\python\\helpers\\pycharm_display', 'D:\\python\\python311\\python311.zip', 'D:\\python\\python311\\Lib', 'D:\\python\\python311\\DLLs', 'D:\\python\\python311', 'D:\\python\\python311\\Lib\\site-packages', 'D:\\PyCharm 2023.2.1\\plugins\\python\\helpers\\pycharm_matplotlib_backend']
    其中
    	1.專案根路徑
        2.site-packages 下載的第三方模組
        3.python內建的,都在環境變數中
    
  • 絕對匯入的優點是清晰明確,能夠避免模組名衝突,並且在程式碼重構時更加穩定。

(2)相對匯入

  • 相對匯入是指從當前模組所在的位置開始的匯入路徑。相對匯入使用與當前模組相關的路徑來匯入模組,而不是從頂層包開始。相對匯入可以透過使用相對匯入語法來實現,例如:

    from . import module
    
  • 相對匯入的優點是更具靈活性,因為它依賴於模組的相對位置,而不是絕對路徑。這使得模組可以更容易地移動或重新命名,而無需更改匯入語句。

(3)Python中相對匯入的規則

  1. 單個點.表示當前目錄。
  2. 兩個點..表示父目錄。
  3. 相對匯入只能在Python包中使用,而不能在指令碼中使用。因為如果指令碼檔案
  4. Python 3中預設禁用隱式相對匯入,必須使用顯式相對匯入。

在實際開發中,通常建議使用絕對匯入,因為它更加明確和穩定。相對匯入在某些情況下可能會引起混亂,尤其是當專案結構較為複雜時。然而,相對匯入在某些特定情況下也是很有用的,特別是在編寫可移植的包或模組時。

(4)建議

  • django專案,如果在同一個app下
    • 建議:相對匯入
    • from .views import index
  • 如果用絕對匯入,會相對來說要寫很多路徑
  • from app01.views import index

4、修改專案名字出現的問題及解決方案

(1)改資料夾名改不了

  • 別的程序開啟了---->重啟電腦

(2)改專案名改不了

  • 專案路徑下的 .idea資料夾----->專案配置
  • 如果開啟專案有問題----> 把 .idea刪除---->再開啟重新執行,它會再次生成的
  • 注意:當我們打包檔案的時候,.idea資料夾是不用加進去的,因為每個電腦的配置都不一樣

(3)改專案中某個檔案的名字(慎改)

  • 首先模組路徑都定好了
  • 如果我們一旦改了----> 專案百分百執行不了
  • 如果改了,那麼我們需要右擊專案----> 找到replace in files---> 替換掉專案中所有叫原來資料夾名的 字串

(4)問題

  • 如果Django專案執行不了了
    • 嘗試django-server刪除重新建立
    • setting中搜django---> 配置專案路徑和配置檔案路徑

image

5、瀏覽器特點

  • 瀏覽器預設訪問http是80埠,https是443埠
  • 先訪問某個頁面【域: 協議,地址,埠】,然後再訪問另一個頁面,只要域一樣就會把當時存在cookie中的值,帶到後端
  • 這種思想方便我們寫登入介面
    • 登入介面:自定義使用者表,UserToken表[使用者登入資訊存在後端--》本質就是django-session]
    • 登入成功--》返回給前端隨機字串
    • 目的:後期它再訪問,要攜帶字串---》我們透過校驗--》確認是我們給的,在UserToken表中有記錄

6、什麼是重寫,什麼是派生,什麼又是過載?

  • 在繼承關係下,子類再寫一遍父類中的方法---》稱之為重寫

    • 重寫的目的是定製新功能
  • 在繼承關係下,子類寫父類中沒有的方法---》稱之為派生方法

  • 過載就是方法名一樣,引數或返回值不一樣----》多了引數或者返回值不一樣

    • python中沒有過載

相關文章