理解下面两个知识点很是重要,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 (每部循环建立的对象)