django框架之drf(部分講解)

吳仁耀發表於2023-02-07

一、認證元件

簡介:

  • 登入認證的限制

  • 認證元件是drf框架給我們提供的認證介面,它能夠在請求進入檢視函式/類前進驗證(例如:認證使用者是否登入),對不符合認證的請求進行攔截並返回校驗失敗的資訊

(1)、登入介面

# 認證是基於登入的介面上面操作的 所以前戲編寫一個簡單的登入介面

models.py
class User(models.Model):  # 簡易的使用者資訊賬號密碼
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)

    def __str__(self):
        return self.username

	'跟User表是一對一外來鍵關聯,儲存使用者登入狀態用的 [這個表可以沒有,如果沒有,把欄位直接寫在User表上也可以]'
class UserToken(models.Model):  # 使用者資訊登入記錄表
    user = models.OneToOneField(to='User', on_delete=models.CASCADE)  # 一對一關聯
    token = models.CharField(max_length=32, null=True)  # 如果使用者沒有登入則沒有值 如果登入則有值

views.py
	'登入介面功能:自動生成路由+登入功能,不用序列化,因此繼承ViewSet即可'
class UserView(ViewSet):
    @action(methods=['POST'], detail=False, url_path='login', url_name='login')
    def login(self, request):
        username = request.data.get('username')     # 獲取使用者名稱與密碼
        password = request.data.get('password')
        user = User.objects.filter(username=username, password=password).first()    # 比對使用者名稱與密碼
        if user:
            token = str(uuid.uuid4())  
            # uuid4 隨機獲得永不重複的字串 機制跟Cookie中的驗證碼一樣
            # 在userToken表中儲存一下:1 從來沒有登入過,插入一條,     2 登入過,修改記錄
            
            
            UserToken.objects.update_or_create(defaults={'token': token}, user=user) 
            # 透過user去UserToken表中查資料,如果能查到,使用defaults的資料更新,如果查不到,直接透過user和defaults的資料新增
            # kwargs 傳入的東西查詢,能找到,使用defaults的更新,否則新增一條
            return Response({'code': 100, 'msg': '登入成功', 'token': token})
        else:
            return Response({'code': 101, 'msg': '使用者名稱或密碼錯誤'})

urls.py
	from rest_framework.routers import SimpleRouter, DefaultRouter
	router = SimpleRouter()
	router.register('users', views.UserView, 'users')
	urlpatterns += router.urls

'''這個時候一個簡單的登入介面就寫好了 每次登入都會更新Token 相當於登入了之前的裝置就無效了 '''

image
update_or_create原始碼如下:

    def update_or_create(self, defaults=None, **kwargs):
        defaults = defaults or {}
        self._for_write = True
        with transaction.atomic(using=self.db):
            try:
                obj = self.select_for_update().get(**kwargs)
            except self.model.DoesNotExist:
                params = self._extract_model_params(defaults, **kwargs)
                obj, created = self._create_object_from_params(kwargs, params, lock=True)
                if created:
                    return obj, created
            for k, v in defaults.items():
                setattr(obj, k, v() if callable(v) else v)
            obj.save(using=self.db)
        return obj, False

(2)、認證元件使用步驟

1.需要寫一個認證類,因此我們需要在應用中另外建立一個py檔案編寫認證類,需要繼承BaseAuthentication這個類

  • 透過檢視原始碼我們可以發現有個authenticate方法需要我們重寫,否則就會報錯,這就是我們需要編寫認證功能的類
class BaseAuthentication:
    def authenticate(self, request):
        raise NotImplementedError(".authenticate() must be overridden.")

    def authenticate_header(self, request):
        pass

2.重寫authenticate方法,在該方法在中實現登入認證

  • token在哪帶的?如何認證它是登入了的?

  • token來判斷是否登陸,登陸了在訪問的時候帶上token,目前階段我們直接在位址列中攜帶token的資料,後面可以在請求頭中新增token的資料

3、如果認證成功,返回兩個值【返回None或兩個值(固定的:當前登入使用者,token)】

4、認證不透過,用AuthenticationFailed類拋異常

程式碼如下:
authenticate.py(認證類)

# 自己寫的認證類,繼承某個類

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .models import UserToken


class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        # 在這裡實現認證,如果是登入的,繼續往後走返回兩個值,如果不是拋異常
        # 請求中是否攜帶token,判斷是否登入,放在位址列中
        token = request.query_params.get('token', None) # 查詢是否有token這個變數名的值,如果沒有就返回None,預設好像返回的是數字
        if token:  # 前端傳入token了,去表中查,如果能查到,登入了,返回兩個值[固定的:當前登入使用者,token]
            user_token = UserToken.objects.filter(token=token).first()
            if user_token:
                return user_token.user, token
            else:
                # 沒有登入拋異常
                raise AuthenticationFailed('token認證失敗')
        else:
            raise AuthenticationFailed('token沒傳')

# 前端傳入的請求頭中的資料從哪取?  GET,body,POST,data

5、認證類的使用

  • 當我們編寫好了認證類中的認證程式碼,接著就需要匯入到檢視層然後使用他
from rest_framework.generics import ListAPIView, RetrieveAPIView
from rest_framework.viewsets import ViewSetMixin
from .authenticate import LoginAuth

# 查詢所有
class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer


    
    
class BookDetailView(ViewSetMixin, RetrieveAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    authentication_classes = [LoginAuth]  # 需要寫一個認證類,需要我們們自行編寫

6、區域性使用和全域性使用

  • 區域性使用:只在某個檢視類中使用【當前檢視類管理的所有介面】
class BookDetailView(ViewSetMixin, RetrieveAPIView):
	authentication_classes = [LoginAuth]
  • 全域性使用:在配置檔案settings.py中編寫,全域性所有介面都生效
REST_FRAMEWORK = {
    	'DEFAULT_AUTHENTICATION_CLASSES':['app01.authenticate.LoginAuth']
	}

注意事項:不要在配置檔案中亂匯入不使用的東西,否則會報錯,但是在匯入類似認證類這樣的檔案時,可以寫上匯入的程式碼然後再修改,最後寫進配置中,這樣可以減少錯誤

  • 區域性禁用:(登陸介面很明顯是不需要校驗是否登陸的,因此有了這個區域性禁用的需求,我們把他的authentication_classes配置成空就是區域性禁用)
class BookDetailView(ViewSetMixin, RetrieveAPIView):
	authentication_classes = []

7、測試路由參考

image

(3)、整體程式碼

views.py

# 查詢所有
class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer


# 查詢單個
class BookDetailView(ViewSetMixin, RetrieveAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    authentication_classes = [LoginAuth]  # 需要寫一個認證類,需要我們們自行編寫

authenticate.py(認證類)

# 自己寫的認證類,繼承某個類

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .models import UserToken


class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        # 在這裡實現認證,如果是登入的,繼續往後走返回兩個值,如果不是拋異常
        # 請求中是否攜帶token,判斷是否登入,放在位址列中
        token = request.query_params.get('token', None) # 查詢是否有token這個變數名的值,如果沒有就返回None,預設好像返回的是數字
        if token:  # 前端傳入token了,去表中查,如果能查到,登入了,返回兩個值[固定的:當前登入使用者,token]
            user_token = UserToken.objects.filter(token=token).first()
            if user_token:
                return user_token.user, token
            else:
                # 沒有登入拋異常
                raise AuthenticationFailed('token認證失敗')
        else:
            raise AuthenticationFailed('token沒傳')

# 前端傳入的請求頭中的資料從哪取?  GET,body,POST,data

urls.py

from django.contrib import admin
from django.urls import path, include
from app01 import views
from rest_framework.routers import SimpleRouter

router = SimpleRouter()  # 後面這個少的用的多,
router.register('user', views.UserView, 'user')
router.register('books', views.BookView, 'books')
router.register('books', views.BookDetailView, 'books')

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/', include(router.urls)),

]

許可權元件

簡介:

  • 在我們使用的一些app或者網頁中(愛奇藝,騰訊影片),都會有一些會員介面(需要購買會員才能夠使用或者觀看),許可權元件就是對使用者的這一許可權進行驗證,在請求進入檢視類/函式程式碼前進行校驗,校驗失敗後直接將請求攔截,並返回校驗失敗的資訊

(1)、許可權元件的使用步驟

模組地址:

from rest_framework.permissions import BasePermission

用法簡介:

# 1、建立一個專門用於編寫許可權元件的py檔案,寫一個許可權類,繼承BasePermission
# 2、重寫has_permission方法(在該方法在中實現許可權認證,在這方法中,request.user就是當前登入使用者)
# 3、如果有許可權,返回True
# 4、沒有許可權,返回False(定製返回的中文: self.message='中文')
# 5、區域性使用和全域性使用
	-區域性使用: # 在某個檢視類中設定介面(不會影響別的檢視類)
        class BookDetailView(ViewSetMixin, RetrieveAPIView):
            permission_classes = [CommonPermission]

	-全域性使用:  # django的settings.py中配置,影響全域性
    	REST_FRAMEWORK = {
            'DEFAULT_PERMISSION_CLASSES': [
                'app01.permissions.CommonPermission',
            ],
        }
  
	-區域性禁用:# 全域性配置區域性禁用
        class BookDetailView(ViewSetMixin, RetrieveAPIView):
            permission_classes = [] 

(2)、程式碼用法

models.py(修改User表的配置後需要重新進行資料庫遷移)

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

perssion.py(許可權類程式碼)

# 寫許可權類,寫一個類,繼承基類BasePermission,重寫has_permission方法,在方法中實現許可權認證,如果有許可權return True ,如果沒有許可權,返回False
from rest_framework.permissions import BasePermission


class CommonPermission(BasePermission):
    def has_permission(self, request, view):
        # 實現許可權的控制  ---》知道當前登入使用者是誰?當前登入使用者是  request.user
        if request.user.user_type == 1:
            return True
        else:
            # 沒有許可權,向物件中放一個屬性 message
            # 如果表模型中,使用了choice,就可以透過  get_欄位名_display()  拿到choice對應的中文
            self.message = '您是【%s】,您沒有許可權' % request.user.get_user_type_display()
            return False

views.py(檢視類程式碼)

class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    # 區域性認證
    authentication_classes = [LoginAuth]
    # 許可權認證(將編寫的頻率類匯入過來)
    permission_classes = [CommentPermission]

image

三、頻率元件

簡介:

  • 頻率是指,控制某個介面訪問頻率(次數)

(1)、頻率元件的使用步驟

模組地址:

from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
# BaseThrottle:需要手動編寫的程式碼較多
# SimpleRateThrottle: 需要手動編寫的程式碼較少(用這個)

用法簡介

# 1、建立一個專門用來編寫頻率元件的py檔案,寫一個頻率類,繼承SimpleRateThrottle
# 2、重寫get_cache_key方法,返回什麼,就以什麼做限制----》ip(使用者id做限制)
# 3、配置一個類屬性scope = 'book_5_m'
# 4、在django的配置檔案中編寫頻率次數
	REST_FRAMEWORK = {
          'DEFAULT_THROTTLE_RATES': {
            'book_5_m': '5/m',  # 一分鐘五次 
        },
    	}
# 5、區域性使用和全域性使用
	-區域性使用: # 隻影響當前的檢視類
    class BookView(ModelViewSet):
        throttle_classes = [CommentThrottle]
     
    -全域性配置:影響全域性
    	REST_FRAMEWORK = {
             'DEFAULT_THROTTLE_CLASSES': ['app01.throttling.CommonThrottle'],

        }
     
    -區域性禁用:
      class BookView(ModelViewSet):
         throttle_classes = [CommentThrottle]

(2)、程式碼用法

throttle.py(頻率類程式碼)

from rest_framework.throttling import BaseThrottle, SimpleRateThrottle


class CommentThrottle(SimpleRateThrottle):
    # 建立一個用於控制頻率的變數名(需要傳入配置檔案)
    scope = 'book_5_m'

    def get_cache_key(self, request, view):
        # 返回什麼就以什麼做限制(request.META.get('REMOTE_ADDR')以IP做限制)
        return request.META.get('REMOTE_ADDR')

檢視類程式碼

class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    # 頻率元件
    throttle_classes = [CommentThrottle]

django配置檔案

REST_FRAMEWORK = {
    # 控制訪問頻率
    'DEFAULT_THROTTLE_RATES': {
        'book_5_m': '5/m',  # 一分鐘五次
    },
}

image

四、過濾的多種用法

簡介:

  • 過濾是指在使用查詢的時候,我們可以透過條件來過濾掉不需要的內容
# restful規範中,要求了,請求地址中帶過濾條件
	-5個介面中,只有一個介面需要有過濾和排序,查詢所有介面

(1)、繼承APIView自己寫

class BookView(APIView):
    def get(self,request):
        # 獲取get請求攜帶的引數
        name=request.query_params.get('name')
        # 透過filter進行過濾
        books = Book.objects.filter(name=name)

(2)、使用drf的內建過濾(繼承GenericAPIview)

模組地址:
該方法為模糊查詢

from rest_framework.filters import SearchFilter

程式碼用法:

class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    # 例項化過濾物件
    filter_backends = [SearchFilter]
    # 指定過濾的欄位(模糊查詢)
    search_fields = ['name', 'price']

搜尋方式:

# name或price中只要有關鍵字就會搜出來 (只能用search=xxx的方式)
	http://127.0.0.1:8000/api/v1/books/?search=西遊記

image

(3)、使用第三方外掛過濾(精準過濾)

第三方外掛:

# 外掛名稱:
	django-filter
    
# 安裝外掛:
	pip3.8 install django-filter

模組地址:

from django_filters.rest_framework import DjangoFilterBackend

程式碼用法:

from rest_framework.viewsets import ModelViewSet
from .models import Book
from .serializer import BookSerializer
from django_filters.rest_framework import DjangoFilterBackend


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

    # 第三方過濾外掛
    filter_backends = [DjangoFilterBackend]
    # 查詢的欄位
    filterset_fields = ['pk','name', 'price']

搜尋方式:

http://127.0.0.1:8000/api/books/?price=99
http://127.0.0.1:8000/api/books/?price=99&name=吶喊

image

4、使用過濾元件

1.定製過濾元件的使用方式與步驟

模組地址:

from rest_framework.filters import BaseFilterBackend

用法簡介:

# 1、建立一個專門用於過濾的py檔案,寫一個類繼承BaseFilterBackend
# 2、重寫filter_queryset方法,在方法內部進行過濾
# 3、直接返回過濾後的物件
# 4、如果沒有過濾直接返回所有資料
# 5、區域性使用
	 -區域性使用:
    class BookView(ViewSetMixin, ListAPIView):
        queryset = Book.objects.all()
        serializer_class = BookSerializer
        # 在類表內填寫過濾的類
        filter_backends = [CommonFilter]  # 可以定製多個,從左往右,依次執行

2.程式碼用法

過濾類程式碼filters.py

from rest_framework.filters import BaseFilterBackend


class CommonFilter(BaseFilterBackend):
    # 重寫的類,編寫過濾吧的內容
    def filter_queryset(self, request, queryset, view):
        # 獲取過濾的條件
        filter_comment = request.query_params.get('price_gt', None)
        # 判斷前端是否傳入過濾條件
        if filter_comment:
            # 根據條件進行賽選內容
            books_queryset_filter = queryset.filter(price__gt=100)
            # 返回過濾後的資料
            return books_queryset_filter
        return queryset

檢視類程式碼

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

    # 第三方過濾外掛
    filter_backends = [CommonFilter]

五、排序的使用

用法簡介

  • 排序需要和自定義過濾繼承同一個父類,需要將排序的物件填入在過濾的列表內,並且放在其他引數的前方
    模組地址:
from rest_framework.filters import OrderingFilter

(2)、程式碼用法

檢視類程式碼

from rest_framework.viewsets import ModelViewSet
from .models import Book
from .serializer import BookSerializer
from django_filters.rest_framework import DjangoFilterBackend
from .filters import CommonFilter
from rest_framework.filters import OrderingFilter


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

    # 第三方過濾外掛(OrderingFilter放在其他過濾引數前)
    filter_backends = [OrderingFilter, DjangoFilterBackend, CommonFilter]
    # 查詢的欄位
    filterset_fields = ['id', 'name', 'price']
    # 指定排序的欄位(-是降序,預設升序)
    ordering_fields = ['price']

搜尋用法

# 預設升序
    http://127.0.0.1:8000/api/books/?price_gt=60&ordering=price
    
# 降序
	http://127.0.0.1:8000/api/books/?price_gt=60&ordering=-price

六、分頁

  • 分頁功能,只有查詢所有介面,才有分頁
  • drf內建了三個分頁器,對應三種分頁方式
  • 內建的分頁類不能直接使用,需要繼承,定製一些引數後才能使用

使用步驟

  • 步驟一:建立一個py檔案編寫分頁用到的自定義類,分頁的三個類並不能直接使用,需要我們進行配置
  • 步驟二:編寫這個自定義類
  • 步驟三:匯入檢視類中,並新增配置

程式碼
page.py(自定義的分頁類)

from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination


# 網頁用它
class CommonPageNumberPagination(PageNumberPagination):
    page_size = 2  # 每頁顯示2條
    page_query_param = 'page'  # page=10  查詢第10頁的資料,每頁顯示2條
    page_size_query_param = 'size'  # page=10&size=5    查詢第10頁,每頁顯示5條
    max_page_size = 5  # 每頁最大顯示10條

'''
page_size 每頁數目
page_query_param 前端傳送的頁數關鍵字名,預設為”page”
page_size_query_param 前端傳送的每頁數目關鍵字名,預設為None
max_page_size 前端最多能設定的每頁數量
'''
    
    
# LimitOffset
class CommonLimitOffsetPagination(LimitOffsetPagination):
    default_limit = 3  # 每頁顯示2條
    limit_query_param = 'limit'  # limit=3   取3條
    offset_query_param = 'offset'  # offset=1  從第一個位置開始,取limit條
    max_limit = 5
    # offset=3&limit=2      0  1 2 3 4 5
'''
default_limit 預設限制,預設值與PAGE_SIZE設定一直
limit_query_param limit引數名,預設’limit’
offset_query_param offset引數名,預設’offset’
max_limit 最大limit限制,預設None
'''

# app 用下面

class CommonCursorPagination(CursorPagination):
    cursor_query_param = 'cursor'  # 查詢引數
    page_size = 2  # 每頁多少條
    ordering = 'id'  # 排序欄位

'''
cursor_query_param:預設查詢欄位,不需要修改
page_size:每頁數目
ordering:按什麼排序,需要指定
'''

views.py

#  分頁功能   必須是繼承GenericAPIView ,如果是繼承APIView,要自己寫(你寫)
from .page import CommonPageNumberPagination as PageNumberPagination
from .page import CommonLimitOffsetPagination as LimitOffsetPagination
from .page import CommonCursorPagination as CommonCursorPagination



class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = []
    authentication_classes = []
    throttle_classes = []
    # 之前的東西一樣用 ,內建的分頁類不能直接使用,需要繼承,定製一些引數後才能使用
    # pagination_class = PageNumberPagination
    #基本分頁方式(基本是這種,網頁端):http://127.0.0.1:8000/api/v1/books/?page=2&size=3

    # pagination_class = LimitOffsetPagination
    # 偏移分頁 http://127.0.0.1:8000/api/v1/books/?limit=4&offset=1
    # 從第一條開始,取4條

    pagination_class = CommonCursorPagination
    # 遊標分頁,只能下一頁,上一頁,不能跳到中間,但它的效率最高,大資料量分頁,使用這種較好

相關文章