一、路由Routers
在 Rest Framework 中提供了兩個 router , 可以幫助我們快速的實現路的自動生成。
必須是繼承 ModelViewSet 的檢視類才能自動生成路由
SimpleRouter
使用方法:
urls.py
# 第一步:匯入routers模組
from rest_framework import routers
# 第二步:例項化得到物件
router = routers.SimpleRouter()
# 第三步:註冊( register('字首', viewset檢視集, 路由的別名) )
router.register('books', views.BooksViewset)
# 第四步:生成路由加入到原路由中
# 方式一:
urlpatterns = [
...
]
urlpatterns += router.urls
# 方式二:
urlpatterns = [
...
url(r'^', include(router.urls))
]
# 形成路由如下
<URLPattern '^books/$' [name='books-list']>
<URLPattern '^books/(?P<pk>[^/.]+)/$' [name='books-detail']>
DefaultRouter
DefaultRouter與SimpleRouter的區別是,DefaultRouter會多附帶一個預設的API根檢視,返回一個包含所有列表檢視的超連結響應資料。
# 前兩條和SimpleRouter一樣
<URLPattern '^books/$' [name='books-list']>
<URLPattern '^books/(?P<pk>[^/.]+)/$' [name='books-detail']>
# 效果也和前兩條類似,
# 如:http://127.0.0.1:8000/books.json
<URLPattern '^books\.(?P<format>[a-z0-9]+)/?$' [name='books-list']>
# http://127.0.0.1:8000/books/1.json
<URLPattern '^books/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$' [name='books-detail']>
# 多了個根路由http://127.0.0.1:8000/
<URLPattern '^$' [name='api-root']>, <URLPattern '^\.(?P<format>[a-z0-9]+)/?$' [name='api-root']>
action的使用
action是為了給繼承自 ModelViewSet 的檢視類中自定義的函式也新增路由
例如下面這樣:
from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
from app01.ser import BooksSerializers
from app01.models import Books
class BooksViewSet(ModelViewSet):
queryset = Books.objects.all()
serializer_class = BooksSerializers
# 這種方法不會自動生成,需要用action配置
def get_num(self, request, pk):
book = self.get_queryset()[:int(pk)]
ser = self.get_serializer(book, many=True)
return Response(ser.data)
使用示例:
action是一個裝飾器,放在被裝飾的函式上方,
method:請求方式
detail:是否帶pk ——>True 表示路徑格式是xxx/<pk>/action方法名/
——False 表示路徑格式是xxx/action方法名/
from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
from rest_framework.decorators import action
from app01.ser import BooksSerializers
from app01.models import Books
class BooksViewSet(ModelViewSet):
queryset = Books.objects.all()
serializer_class = BooksSerializers
@action(methods=['GET', 'POST'], detail=True)
def get_num(self, request, pk):
book = self.get_queryset()[:int(pk)] # 獲取前幾條資料
ser = self.get_serializer(book, many=True)
return Response(ser.data)
# 生成路由如下
http://127.0.0.1:8000/books/2/get_num/
<URLPattern '^books/(?P<pk>[^/.]+)/get_num/$' [name='books-get-num']>
二、認證
認證的寫法
- 寫一個認證類,繼承 BaseAuthentication,重寫 authenticate, 認證的邏輯寫在裡面,認證通過,返回兩個值,一個值給Request物件的user, 認證失敗,拋異常:APIException或者AuthenticationFailed
- 將認證類新增到需要認證檢視類的
authentication_classes = [認證類1]
中 - 全域性使用,還是區域性使用
# 全域性使用,在setting.py中配置
REST_FRAMEWORK={
"DEFAULT_AUTHENTICATION_CLASSES":["app01.app_auth.MyAuthentication",]
}
# 區域性使用,在檢視類上寫
authentication_classes=[MyAuthentication]
# 區域性禁用
authentication_classes=[]
認證原始碼分析
1、APIView重寫as_view方法使之沒有csrf認證——>但還是正常執行 dispatch 方法,但是 dispatch方法被 APIView重寫了——>dispatch 中執行了 self.initial 認證方法——>有認證,許可權,頻率
2、現在只是看認證原始碼self.perform_authentication(request)
3、但是self.perform_authentication(request)
就一句話:request.user
,那麼就需要去 drf 的 Request 物件中找 user 屬性(方法)
@property
def user(self):
# 先去判斷當前物件中有沒有'_user'這個屬性,一開始肯定是沒有的,因為使用者是沒有登入的
if not hasattr(self, '_user'):
with wrap_attributeerrors():
# 沒有使用者,認證出使用者
self._authenticate()
# 有使用者,直接返回使用者
return self._user
4、Request 類中的 user 方法,剛開始來,沒有_user
,走 self._authenticate()
5、核心,就是Request類中的 _authenticate(self)
def _authenticate(self):
# 遍歷拿到一個認證器,進行認證
# self.authenticators 配置的一堆認證類產生的認證類物件組成的 list
# self.authenticators 就是在檢視類中配置的:authentication_classes = [認證類1,認證類2] 的一個個認證類的物件:
————>self.authenticators ==》 [認證類1物件,認證類2物件]
for authenticator in self.authenticators:
try:
# 認證器呼叫認證方法authenticate(認證類物件self,request物件)
"""
def authenticate(self, request):
return (self.force_user, self.force_token)
"""
# 返回值:登入的使用者與認證的資訊組成的 tuple
# 並且該方法被try包裹,就代表該方法會拋異常,拋異常就代表認證失敗
user_auth_tuple = authenticator.authenticate(self) # self是request物件
except exceptions.APIException:
self._not_authenticated()
raise
# 返回值的處理
if user_auth_tuple is not None:
self._authenticator = authenticator
# 如果有返回值,就將 "登入使用者" 與 "登入認證" 分別儲存到 request.user / request.auth
self.user, self.auth = user_auth_tuple
return
# 如果返回值user_auth_tuple為空,代表認證通過,但是沒有 "登入使用者" 與 "登入認證資訊",代表遊客
self._not_authenticated()
認證元件的使用
1、寫一個認證類,繼承 BaseAuthentication,重寫 authenticate
# app01_auth.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from app01.models import UserToken
class TokenAuthentication(BaseAuthentication):
def authenticate(self, request):
# 認證邏輯,如果認證通過,返回兩個值
# 如果認證失敗,丟擲AuthenticationFailed異常
token = request.data.get('token')
if token:
user_token = UserToken.objects.filter(token=token).first()
# 認證通過
if user_token:
return UserToken.user, token
else:
raise AuthenticationFailed('認證失敗')
else:
raise AuthenticationFailed('請求地址中需要帶token')
2、將認證類新增到需要認證檢視類的authentication_classes = [認證類1]
中
# views.py
from rest_framework.viewsets import ModelViewSet
from rest_framework.views import APIView
from rest_framework.response import Response
from app01.models import Books, UserInfo, UserToken
from app01.ser import BooksSerializer
from app01.app01_auth import TokenAuthentication
import uuid
# 檢視Books需要經過認證才能檢視
class BooksView(ModelViewSet):
authentication_classes = [TokenAuthentication]
queryset = Books.objects.all()
serializer_class = BooksSerializer
# 登入檢視,登入後獲得token,後續用token認證
class LoginView(APIView):
def post(self, request):
response_msg = {'status': 200, 'msg': ''}
username = request.data.get('username')
password = request.data.get('password')
user_obj = UserInfo.objects.filter(username=username, password=password).first()
if user_obj:
# 登入成功生成一個隨機字串
token = uuid.uuid4()
# 存到UserToken表中,update_or_create有就更新,沒有就新增
UserToken.objects.update_or_create(defaults={'token': token}, user=user_obj)
response_msg['msg'] = '登入成功'
response_msg['token'] = token
else:
response_msg['msg'] = '賬戶或密碼錯誤'
response_msg['status'] = 204
return Response(response_msg)