一、認證元件
簡介:
-
登入認證的限制
-
認證元件是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 相當於登入了之前的裝置就無效了 '''
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、測試路由參考
(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]
三、頻率元件
簡介:
- 頻率是指,控制某個介面訪問頻率(次數)
(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', # 一分鐘五次
},
}
四、過濾的多種用法
簡介:
- 過濾是指在使用查詢的時候,我們可以透過條件來過濾掉不需要的內容
# 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=西遊記
(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=吶喊
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
# 遊標分頁,只能下一頁,上一頁,不能跳到中間,但它的效率最高,大資料量分頁,使用這種較好