將上一篇文章中的寫法進一步封裝簡化數據庫
urlsdjango
from app01 import views urlpatterns = [ ...... url(r'^authors/$', views.AuthorView.as_view(), name="author"), url(r'^authors/(?P<pk>\d+)/$', views.AuthorDetailView.as_view(), name="detail_author"), ]
還要寫一個ModelSerializer,方法與上一篇博文中相同
viewsjson
from rest_framework import mixins from rest_framework import generics #GenericAPIView繼承了APIView class AuthorView(mixins.ListModelMixin, #查看全部 mixins.CreateModelMixin, #添加 generics.GenericAPIView): queryset = Author.objects.all() serializer_class = AuthorModelSerializers def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) class AuthorDetailView(mixins.DestroyModelMixin, #刪除 mixins.RetrieveModelMixin, #查看單條 mixins.UpdateModelMixin, #更新 generics.GenericAPIView): queryset = Author.objects.all() serializer_class = AuthorModelSerializers def get(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs): return self.update(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs)
這是最終封裝版本,關鍵在於讓兩條不一樣的url(帶pk值和不帶pk值)都匯聚到同一個視圖類中api
urls.py:app
url(r'^authors/$', views.AuthorView.as_view({"get":"list","post":"create"}),name="author"), url(r'^authors/(?P<pk>\d+)$', views.AuthorView.as_view({ 'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy' }),name="detail_author"),
views.py:dom
from rest_framework import viewsets class AuthorView(viewsets.ModelViewSet): queryset = Author.objects.all() serializer_class = AuthorModelSerializers
如下面這個url爲例子,咱們能夠看到這條url最大的變化就是as_view後面傳值了,所以要看看是如何處理的ide
url(r'^authors/$', views.AuthorView.as_view({"get": "list", "post": "create"}), name="author")
咱們須要看看此時的as_view是如何用一個視圖類處理兩條url的,首先尋找這個as_view方法在哪裏,事實上它已經不是原來的as_view方法了
AuthorView類-ModelViewSet類-GenericViewSet類-ViewSetMixin類post
在ViewSetMixin類中找到as_view方法this
def as_view(cls, actions=None, **initkwargs): ...... return csrf_exempt(view)
找到同在ViewSetMixin類中的view:url
def as_view(cls, actions=None, **initkwargs): ...... def view(request, *args, **kwargs): ...... for method, action in actions.items(): #循環actions{"get": "list", "post": "create"} handler = getattr(self, action) #handler = self.list或handler = self.create setattr(self, method, handler) #self.get = self.list或self.post = self.create ...... return self.dispatch(request, *args, **kwargs)
Django啓動後的url就等同於下面的狀況,等待用戶訪問
url(r'^authors/$', ViewSetMixin.view({"get": "list", "post": "create"}), name="author")
用戶訪問開始後:
在views.APIView中找到self.dispatch:
class APIView(View): def dispatch(self, request, *args, **kwargs): try: ...... # Get the appropriate handler method if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), #此處的request.method.lower()是字符串,get或post self.http_method_not_allowed) #由於上面已經經過反射綁定self.get = self.list或self.post = self.create, #所以這裏: #handler = self.list或self.create else: handler = self.http_method_not_allowed response = handler(request, *args, **kwargs) #這裏去找self.list或self.create,將執行的結果返回給response self.response = self.finalize_response(request, response, *args, **kwargs) return self.response #將ListModelMixin處理後的結果返回給請求者
self.list或self.create在ModelViewSet類的父類mixins.ListModelMixin或mixins.CreateModelMixin中
class ModelViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet): """ A viewset that provides default `create()`, `retrieve()`, `update()`, `partial_update()`, `destroy()` and `list()` actions. """ pass
ListModelMixin類將數據處理並序列化後返回給APIView下的dispatch
class ListModelMixin(object): """ List a queryset. """ def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data)
思考:viewsets.ModelViewSet經過覆蓋APIView中同名的as_view來實現了新功能,若是有需求的話咱們也能夠經過覆蓋同名方法來實現新的功能,例如咱們能夠本身寫一個list方法來實現不一樣的需求
#認證組件 self.perform_authentication(request) #權限組件 self.check_permissions(request) #頻率組件 self.check_throttles(request)
在app01.service.auth.py:
from rest_framework import exceptions from rest_framework.authentication import BaseAuthentication class TokenAuth(BaseAuthentication): def authenticate(self, request): token = request.GET.get("token") token_obj = Token.objects.filter(token=token).first() if not token_obj: #認證失敗拋錯,被源碼中的try捕獲 raise exceptions.AuthenticationFailed("驗證失敗!") return token_obj.user, token_obj.token #須要返回一個元組
在views.py:
def get_random_str(user): import hashlib, time ctime = str(time.time()) md5 = hashlib.md5(bytes(user, encoding="utf8")) md5.update(bytes(ctime, encoding="utf8")) return md5.hexdigest() from django.http import JsonResponse class loginView(APIView): authentication_classes = [TokenAuth] def post(self, request): res = {"code": 1000, "msg": None} user = request.data.get("user") pwd = request.data.get("pwd") user_obj = User.objects.filter(name=user, pwd=pwd).first() if not user_obj: res["code"] = 1001 res["msg"] = "用戶名或密碼錯誤" else: token = get_random_str(user) Token.objects.update_or_create(user=user_obj, defaults={"token": token}) res["token"] = token return JsonResponse(res, json_dumps_params={"ensure_ascii": False})
咱們知道在APIView類中能夠找到as_view,而此時的as_view又指向了父類View中的as_view,此時父類as_view又會return dispatch,所以咱們在APIView類中找到dispatch方法,從這裏開始看源碼的執行過程。
class APIView(View): def dispatch(self, request, *args, **kwargs): self.initial(request, *args, **kwargs) #這一步就是在處理認證、權限、頻率
↓
class APIView(View): def initial(self, request, *args, **kwargs): self.perform_authentication(request) #認證組件 self.check_permissions(request) #權限組件 self.check_throttles(request) #訪問頻率組件
↓
class APIView(View): def perform_authentication(self, request): request.user
這個request是Request類的實例化對象,所以咱們要去Request下面去找user方法
↓
class Request(object): @property def user(self): if not hasattr(self, '_user'): with wrap_attributeerrors(): self._authenticate() #調用user過程其實就是在執行這個方法 return self._user
查看self._authenticate
↓
class Request(object): def _authenticate(self): for authenticator in self.authenticators: #循環包含着一個個認證類實例的列表,此時就是一個[TokenAuth(),] try: user_auth_tuple = authenticator.authenticate(self) #將視圖中的authenticate返回結果賦值給user_auth_tuple,此時傳進去的self是Request類的實例化對象 except exceptions.APIException: #驗證失敗拋錯 self._not_authenticated() raise if user_auth_tuple is not None: #若是不爲空 self._authenticator = authenticator self.user, self.auth = user_auth_tuple #user_auth_tuple是個元祖,分紅了兩個變量,這兩個變量能夠爲下面的權限組件所利用 return #認證成功後返回
self.authenticators是什麼?
往上走,發現構建request時傳進來的參數
class APIView(View): def initialize_request(self, request, *args, **kwargs): ...... return Request( request, parsers=self.get_parsers(), authenticators=self.get_authenticators(), #在這裏 negotiator=self.get_content_negotiator(), parser_context=parser_context )
點進去看看,發現就是self.authentication_classes循環的結果
def get_authenticators(self): """ Instantiates and returns the list of authenticators that this view can use. """ return [auth() for auth in self.authentication_classes] #列表解析式,循環的是咱們本身在視圖中寫的一個個認證類
所以可見,self.authenticators就是包含着一個個認證類實例對象的列表
authenticator.authenticate(self)是什麼意思?
咱們再回到_authenticate方法中看看這句話
authenticator.authenticate(self)
實例化對象調本身的方法是不須要傳self的,所以這是個形參,我要知道這個self是誰
那麼這個self是誰?
要往上一級一級找,上一級是_authenticate(self),誰調用的?
找到user(self),誰調用的user(self)?
class APIView(View): def perform_authentication(self, request): request.user
request.user調的user,所以self就是這個新構建的request,這個request是Request類的實例化對象
GET訪問時加上數據庫中已有的一個token就能經過驗證
http://127.0.0.1:8000/books/?token=1a54a64ee1111738c5d8b7b5487e801b
若是咱們本身不設authentication_classes,那麼就會去父類APIView中找,裏面有這麼一段代碼
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
api_settings是APISettings類的一個實例化對象
api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
api_settings.DEFAULT_AUTHENTICATION_CLASSES會去找settings.py中的REST_FRAMEWORK
所以咱們本身在settings.py設置這個REST_FRAMEWORK就能夠
REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.TokenAuth"] }
["app01.utils.TokenAuth"]這個值是具體路徑,也能夠是元祖
若是某個視圖(好比Login)不但願它通過全局認證,那麼能夠在視圖類中添加一個authentication_classes = []便可