理解下面兩個知識點很是重要,django-rest-framework源碼中處處都是基於CBV和麪向對象的封裝javascript
(1)面向對象封裝的兩大特性html
把同一類方法封裝到類中 將數據封裝到對象中
(2)CBVjava
CBV(class base views) 就是在視圖裏使用類處理請求。 對應的還有 FBV(function base views) 就是在視圖裏使用函數處理請求。python
基於反射實現根據請求方式不一樣,執行不一樣的方法sql
原理:url-->view方法-->dispatch方法(反射執行其它方法:GET/POST/PUT/DELETE等等)shell
CBV(class base views) 就是在視圖裏使用類處理請求。數據庫
Python是一個面向對象的編程語言,若是隻用函數來開發,有不少面向對象的優勢就錯失了(繼承、封裝、多態)。因此Django在後來加入了Class-Based-View。可讓咱們用類寫View。這樣作的優勢主要下面兩種:django
若是咱們要寫一個處理GET方法的view,用函數FBV寫的話是下面這樣。編程
from django.http import HttpResponse def my_view(request): if request.method == 'GET': return HttpResponse('OK')
若是用class-based view寫的話,就是下面這樣api
from django.http import HttpResponse from django.views import View class MyView(View): def get(self, request): return HttpResponse('OK')
Django的url是將一個請求分配給可調用的函數的,而不是一個class。針對這個問題,class-based view提供了一個as_view()
靜態方法(也就是類方法),調用這個方法,會建立一個類的實例,而後經過實例調用dispatch()
方法,dispatch()
方法會根據request的method的不一樣調用相應的方法來處理request(如get()
, post()
等)。到這裏,這些方法和function-based view差很少了,要接收request,獲得一個response返回。若是方法沒有定義,會拋出HttpResponseNotAllowed異常。
在url中,就這麼寫:
# urls.py from django.conf.urls import url from myapp.views import MyView urlpatterns = [ url(r'^index/$', MyView.as_view()), ]
類的屬性能夠經過兩種方法設置,第一種是常見的Python的方法,能夠被子類覆蓋。
from django.http import HttpResponse from django.views import View class GreetingView(View): name = "yuan" def get(self, request): return HttpResponse(self.name) # You can override that in a subclass class MorningGreetingView(GreetingView): name= "alex"
第二種方法,你也能夠在url中指定類的屬性:
在url中設置類的屬性Python
urlpatterns = [ url(r'^index/$', GreetingView.as_view(name="egon")), ]
要理解django的class-based-view(如下簡稱cbv),首先要明白django引入cbv的目的是什麼。在django1.3以前,generic view也就是所謂的通用視圖,使用的是function-based-view(fbv),亦即基於函數的視圖。有人認爲fbv比cbv更pythonic,竊覺得否則。python的一大重要的特性就是面向對象。而cbv更能體現python的面向對象。cbv是經過class的方式來實現視圖方法的。class相對於function,更能利用多態的特定,所以更容易從宏觀層面上將項目內的比較通用的功能抽象出來。關於多態,很少解釋,有興趣的同窗本身Google。總之能夠理解爲一個東西具備多種形態(的特性)。cbv的實現原理經過看django的源碼就很容易明白,大致就是由url路由到這個cbv以後,經過cbv內部的dispatch方法進行分發,將get請求分發給cbv.get方法處理,將post請求分發給cbv.post方法處理,其餘方法相似。怎麼利用多態呢?cbv裏引入了mixin的概念。Mixin就是寫好了的一些基礎類,而後經過不一樣的Mixin組合成爲最終想要的類。
因此,理解cbv的基礎是,理解Mixin。Django中使用Mixin來重用代碼,一個View Class能夠繼承多個Mixin,可是隻能繼承一個View(包括View的子類),推薦把View寫在最右邊,多個Mixin寫在左邊。
mixin模式
class X(object): def f(self): print( 'x') class A(X): def f(self): print('a') def extral(self): print('extral a') class B(X): def f(self): print('b') def extral(self): print( 'extral b') class C(A, B, X): def f(self): super(C, self).f() print('c') print(C.mro()) c = C() c.f() c.extral()
這樣作也能夠輸出結果
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.X'>, <class 'object'>] # 繼承的順序是 A-->B-->X-->object 這了的object在python3中是一切類的基類,包括object類自己。 a c extral a # 雖然類C中沒有實現接口extral(),卻調用了父類A中的extral()方法
這樣的繼承雖然能夠實現功能,可是有一個很明顯的問題,那就是在面向對象中,必定要指明一個類究竟是什麼。也就是說,若是我想構造一個類,假如是Somthing
,那麼我想讓這個類實現會飛,會游泳,會跑,三種行爲,我能夠這樣作,同時繼承,鳥,魚,馬三個類,像這樣
class Bird: def fly(self): print('fly') class Fish: def swim(self): print('swim') class Horse: def run(self): print('run') class Something(Bird, Fish, Horse): pass s = Something() s.fly() s.swim() s.run()
輸出
fly
swim
run
但是實現會跑,會飛,會游泳的三種行爲,可是這個類究竟是什麼,是魚,是馬,仍是鳥,也就是說不知道Something究竟是個什麼類。爲了解決這個問題,咱們能夠引用mixin
模式。改寫以下
class BirdMixin: def fly(self): print('fly') class FishMixin: def swim(self): print('swim') class Horse: def run(self): print('run') class Something(BirdMixin, FishMixin, Horse): pass
這樣就解決了上面的問題,也許你會發現,這其實沒有什麼變化,只是在類的命名加上了以Mixin
結尾,其實這是一種默認的聲明,告訴你,Something
類實際上是一種馬,父類是Horse
Horse,繼承其餘兩個類,只是爲了調用他們的方法而已,這種叫作mixin
模式,在drf
的源碼種會用到
例如drf
中的generics
路徑爲rest_framework/generics.py
class CreateAPIView(mixins.CreateModelMixin, GenericAPIView): pass class ListAPIView(mixins.ListModelMixin, GenericAPIView): pass class RetrieveAPIView(mixins.RetrieveModelMixin, GenericAPIView): pass
至關於每多一次繼承,子類可調用的方法就更多了。
簡單實例
先建立一個project和一個app(我這裏命名爲api)
首先要在settings的app中添加
# Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'api', 'corsheaders', # 解決跨域問題 修改1 ]
from django.contrib import admin from django.urls import path from django.conf.urls import url from api.views import AuthView from api.views import OrderView,UserInfoView from api.appview.register import registerView from django.views.generic.base import TemplateView # 一、增長該行 urlpatterns = [ path('admin/', admin.site.urls), path(r'',TemplateView.as_view(template_name='index.html')), #二、 增長該行 url(r'^api/v1/auth/$', AuthView.as_view()), # 登陸 url(r'^api/v1/order/$', OrderView.as_view()), # 用戶認證 url(r'^api/v1/info/',UserInfoView.as_view()), # 用戶權限 url(r'^home/register/$', registerView.as_view()), # 註冊 ]
一個保存用戶的信息
一個保存用戶登陸成功後的token
from django.db import models # Create your models here. class User(models.Model): USER_TYPE = ( (1, '普通用戶'), (2, 'VIP'), (3, 'SVIP') ) username = models.CharField(max_length=32,unique=True) password = models.CharField(max_length=32) age = models.CharField(max_length=32) e_mail = models.EmailField() user_type = models.IntegerField(choices=USER_TYPE) create_time = models.DateTimeField(auto_now_add=True) update_time = models.DateTimeField(auto_now=True) def __str__(self): return 'username: %s,password: %s' %(self.username,self.password) # return 'username: {},password: {}'.format(self.username, self.password) class Meta: db_table = 'user' verbose_name = verbose_name_plural = '用戶信息表' class userToken(models.Model): user = models.OneToOneField(to='User',on_delete=models.DO_NOTHING) # 注意:在數據中關聯字段名稱叫user_id token = models.CharField(max_length=60) """定義每一個數據對象的顯示信息""" def __unicode__(self): # return self.user 使用 def __str__(self):報錯 , 使用 __unicode__ 就 OK了。 return self.user """定義每一個數據對象的顯示信息""" def __str__(self): return self.token # 這個返回值是幹什麼的? 這裏 不能寫 user 由於user 對應的是 user_id 是int 類型,服務端會報錯。 class Meta: db_table = 'userToken' verbose_name = verbose_name_plural = '用戶token表' # def __str__(self): 的做用 # return 'username: %s,password: %s' %(self.username,self.password) # 在終端中執行 python manage.py shell # from api.models import User # User.objects.get(id=1) # 便可返回以下 信息, # <User: username: wang,password: 123456>
用戶登陸(返回token並保存到數據庫)
from django.shortcuts import render # Create your views here. import time from api import models from django.http import JsonResponse from rest_framework.views import APIView from rest_framework.request import Request from rest_framework import exceptions from rest_framework.authentication import BasicAuthentication from django.shortcuts import render,HttpResponse from api.utils.permission import SVIPPermission,MyPermission ORDER_DICT = { 1:{ 'name':'apple', 'price':15 }, 2:{ 'name':'orange', 'price':30 } } def md5(user): import hashlib import time ctime = str(time.time()) print(ctime) m = hashlib.md5(bytes(user,encoding='utf-8')) print(m) m.update(bytes(ctime,encoding='utf-8')) print(m) usertoken = m.hexdigest() print(usertoken) return usertoken class AuthView(APIView): authentication_classes = [] # 裏面爲空,表明不須要認證 permission_classes = [] def post(self,request,*args,**kwargs): print('參數',request) ret = {'code':1000,'msg':None,'token':None} try: # 參數是datadict 形式 usr = request.data.get('username') pas = request.data.get('password') # usr = request._request.POST.get('username') # pas = request._request.POST.get('password') # usr = request.POST.get('username') # pas = request.POST.get('password') print(usr) print(pas) # obj = models.User.objects.filter(username='yang', password='123456').first() obj = models.User.objects.filter(username=usr,password=pas).first() # obk =models.userToken.objects.filter(token='9c979c316d4ea42fd998ddf7e8895aa4').first() # print(obk.token) print('******') print(obj) print(type(obj)) print(obj.username) print(obj.password) if not obj: ret['code'] = '1001' ret['msg'] = '用戶名或者密碼錯誤' return JsonResponse(ret) # 裏爲了簡單,應該是進行加密,再加上其餘參數 # token = str(time.time()) + usr token = md5(usr) print(token) models.userToken.objects.update_or_create(user=obj, defaults={'token': token}) ret['token'] = token ret['msg'] = '登陸成功' #ret['token'] = token except Exception as e: ret['code'] = 1002 ret['msg'] = '請求異常' return JsonResponse(ret)
api/appview/register.py
# FileName : register.py # Author : Adil # DateTime : 2019/7/19 5:52 PM # SoftWare : PyCharm from api import models from django.http import JsonResponse from rest_framework.views import APIView import time from api.common.DBHandle import DataBaseHandle import datetime class registerView(APIView): def changeinfo(self,*args): pass def post(self,request,*args,**kwargs): print('參數', request) print(request.data) ret = {'code': 1000, 'msg': None} try: # 參數是datadict 形式 usr = request.data.get('username') pas = request.data.get('password') age = request.data.get('age') user_type = request.data.get('user_type') e_mail = request.data.get('email') localTime = time.localtime(time.time()) tmp_time = time.strftime("%Y-%m-%d %H:%M:%S", localTime) print(usr) print(tmp_time) print(e_mail) ct = tmp_time ut = tmp_time host = '127.0.0.1' username = 'username' password = 'password' database = 'dbname' port = 3306 # 實例化 數據庫 鏈接 dbcon = DataBaseHandle(host, username, password, database, port) sql = "select username from user where username = '%s' " %(usr) l1 = dbcon.selectDb(sql) print(age) if l1==1: ret['msg'] = '用戶已存在!' else: # obj = models.User.objects.filter(username=usr, password=pas).first() #print(obj.age) # models.User.objects.update_or_create(username='yang', password='123456',age='19') print('else') print(usr,pas,age,e_mail,ct,ut) #sql = "insert into user(username,password,age,e_mail) values ('%s','%s','%s','%s')" % (usr, pas,age,e_mail) # models.User.objects.update_or_create(username=usr, password=pas, age=age,e_mail= e_mail,create_time=ct,update_time=ut) # obj = models.User.objects.filter(username='yang', password='123456').first() # tt = dbcon.insertDB(sql) user = models.User(username=usr, password=pas, age=age,user_type=user_type,e_mail= e_mail) user.save() print(models.User.objects.all()) print('*******') ret['msg'] = '註冊成功' # if tt==1: # ret['msg'] = '註冊成功' except Exception as e: ret['code'] = 1002 ret['msg'] = '請求異常' return JsonResponse(ret)
註冊
註冊成功後,數據存入user表
userToken數據表
基於上面的例子,添加一個認證的類
path('api/v1/order/',OrderView.as_view()),
from django.shortcuts import render # Create your views here. import time from api import models from django.http import JsonResponse from rest_framework.views import APIView from rest_framework.request import Request from rest_framework import exceptions from rest_framework.authentication import BasicAuthentication from django.shortcuts import render,HttpResponse from api.utils.permission import SVIPPermission,MyPermission ORDER_DICT = { 1:{ 'name':'apple', 'price':15 }, 2:{ 'name':'orange', 'price':30 } } def md5(user): import hashlib import time ctime = str(time.time()) print(ctime) m = hashlib.md5(bytes(user,encoding='utf-8')) print(m) m.update(bytes(ctime,encoding='utf-8')) print(m) usertoken = m.hexdigest() print(usertoken) return usertoken class AuthView(APIView): authentication_classes = [] # 裏面爲空,表明不須要認證 permission_classes = [] def post(self,request,*args,**kwargs): print('參數',request) ret = {'code':1000,'msg':None,'token':None} try: # 參數是datadict 形式 usr = request.data.get('username') pas = request.data.get('password') # usr = request._request.POST.get('username') # pas = request._request.POST.get('password') # usr = request.POST.get('username') # pas = request.POST.get('password') print(usr) print(pas) # obj = models.User.objects.filter(username='yang', password='123456').first() obj = models.User.objects.filter(username=usr,password=pas).first() # obk =models.userToken.objects.filter(token='9c979c316d4ea42fd998ddf7e8895aa4').first() # print(obk.token) print('******') print(obj) print(type(obj)) print(obj.username) print(obj.password) if not obj: ret['code'] = '1001' ret['msg'] = '用戶名或者密碼錯誤' return JsonResponse(ret) # 裏爲了簡單,應該是進行加密,再加上其餘參數 # token = str(time.time()) + usr token = md5(usr) print(token) models.userToken.objects.update_or_create(user=obj, defaults={'token': token}) ret['token'] = token ret['msg'] = '登陸成功' #ret['token'] = token except Exception as e: ret['code'] = 1002 ret['msg'] = '請求異常' return JsonResponse(ret) class Authentication(APIView): '''認證''' def authenticate(self,request): token = request._request.GET.get('token') print(token) token_obj = models.userToken.objects.filter(token=token).first() if not token_obj: raise exceptions.AuthenticationFailed('用戶認證失敗') # 在rest framework內部會將這兩個字段賦值給request,以供後續操做使用 return (token_obj.user, token_obj) def authenticate_header(self, request): pass class OrderView(APIView): '''訂單業務''' authentication_classes = [Authentication,] #添加認證 # permission_classes = [] def get(self,request,*args,**kwargs): print("~~~~~~") print(request.user) print(request.auth) print("~~~~~~") ret = {'code':1000,'msg':None,'data':None} try: ret['data'] = ORDER_DICT except Exception as e: pass return JsonResponse(ret)
請求的時候沒有帶token,能夠看到會顯示「用戶認證失敗」
加上token從新請求
以上是簡單的局部認證。下面介紹一下全局認證
全局配置方法:
api文件夾下面新建文件夾utils,再新建auth.py文件,裏面寫上認證的類
auth.py
# api/utils/auth.py from rest_framework import exceptions from api import models class Authentication(object): '''用於用戶登陸驗證''' def authenticate(self,request): token = request._request.GET.get('token') token_obj = models.UserToken.objects.filter(token=token).first() if not token_obj: raise exceptions.AuthenticationFailed('用戶認證失敗') #在rest framework內部會將這兩個字段賦值給request,以供後續操做使用 return (token_obj.user,token_obj) def authenticate_header(self, request): pass
settings.py
#設置全局認證 REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.Authentication',], #裏面寫你的認證的類的路徑 }
在settings裏面設置的全局認證,全部業務都須要通過認證,若是想讓某個不須要認證,只須要在其中添加下面的代碼:
authentication_classes = [] #裏面爲空,表明不須要認證
from django.shortcuts import render # Create your views here. import time from api import models from django.http import JsonResponse from rest_framework.views import APIView from rest_framework.request import Request from rest_framework import exceptions from rest_framework.authentication import BasicAuthentication from django.shortcuts import render,HttpResponse from api.utils.permission import SVIPPermission,MyPermission ORDER_DICT = { 1:{ 'name':'apple', 'price':15 }, 2:{ 'name':'orange', 'price':30 } } def md5(user): import hashlib import time ctime = str(time.time()) print(ctime) m = hashlib.md5(bytes(user,encoding='utf-8')) print(m) m.update(bytes(ctime,encoding='utf-8')) print(m) usertoken = m.hexdigest() print(usertoken) return usertoken class AuthView(APIView): authentication_classes = [] # 裏面爲空,表明不須要認證 permission_classes = [] def post(self,request,*args,**kwargs): print('參數',request) ret = {'code':1000,'msg':None,'token':None} try: # 參數是datadict 形式 usr = request.data.get('username') pas = request.data.get('password') # usr = request._request.POST.get('username') # pas = request._request.POST.get('password') # usr = request.POST.get('username') # pas = request.POST.get('password') print(usr) print(pas) # obj = models.User.objects.filter(username='yang', password='123456').first() obj = models.User.objects.filter(username=usr,password=pas).first() # obk =models.userToken.objects.filter(token='9c979c316d4ea42fd998ddf7e8895aa4').first() # print(obk.token) print('******') print(obj) print(type(obj)) print(obj.username) print(obj.password) if not obj: ret['code'] = '1001' ret['msg'] = '用戶名或者密碼錯誤' return JsonResponse(ret) # 裏爲了簡單,應該是進行加密,再加上其餘參數 # token = str(time.time()) + usr token = md5(usr) print(token) models.userToken.objects.update_or_create(user=obj, defaults={'token': token}) ret['token'] = token ret['msg'] = '登陸成功' #ret['token'] = token except Exception as e: ret['code'] = 1002 ret['msg'] = '請求異常' return JsonResponse(ret) class OrderView(APIView): '''訂單業務''' # authentication_classes = [] # permission_classes = [] def get(self,request,*args,**kwargs): print("~~~~~~") print(request.user) print(request.auth) print("~~~~~~") ret = {'code':1000,'msg':None,'data':None} try: ret['data'] = ORDER_DICT except Exception as e: pass return JsonResponse(ret)
再測試一下咱們的代碼
不帶token發請求
rest_framework裏面內置了一些認證,咱們本身寫的認證類都要繼承內置認證類 "BaseAuthentication"
class BaseAuthentication(object): """ All authentication classes should extend BaseAuthentication. """ def authenticate(self, request): """ Authenticate the request and return a two-tuple of (user, token). """ #內置的認證類,authenticate方法,若是不本身寫,默認則拋出異常 raise NotImplementedError(".authenticate() must be overridden.") def authenticate_header(self, request): """ Return a string to be used as the value of the `WWW-Authenticate` header in a `401 Unauthenticated` response, or `None` if the authentication scheme should return `403 Permission Denied` responses. """ #authenticate_header方法,做用是當認證失敗的時候,返回的響應頭 pass
本身寫的Authentication必須繼承內置認證類BaseAuthentication
# FileName : auth.py # Author : Adil # DateTime : 2019/7/30 4:29 PM # SoftWare : PyCharm from rest_framework import exceptions from api import models from rest_framework.authentication import BaseAuthentication class Authentication(BaseAuthentication): '''用於用戶登陸驗證''' def authenticate(self,request): # token = request._request.GET.get('token') token = request.GET.get('token') # 同 request._request.GET.get('token') # token = request.query_params.get("token") # 同request.GET.get("token") print('***') print(token) token_obj = models.userToken.objects.filter(token=token).first() print(token_obj) if not token_obj: raise exceptions.AuthenticationFailed('用戶認證失敗') #在rest framework內部會將這兩個字段賦值給request,以供後續操做使用 return (token_obj.user,token_obj) # 這兩個返回值分別對應 models.py 中 userToken 的user和 token def authenticate_header(self, request): pass
rest_framework裏面還內置了其它認證類,咱們主要用到的就是BaseAuthentication,剩下的不多用到
本身寫認證類方法梳理
(1)建立認證類
(2)authenticate()返回值(三種)
(3)局部使用
(4)全局使用
#設置全局認證
REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.Authentication',] }
源碼流程
--->>dispatch
--封裝request
---獲取定義的認證類(全局/局部),經過列表生成式建立對象
---initial
----peform_authentication
-----request.user (每部循環建立的對象)