在以前的代码习惯中,一般使用一个url对应一个视图函数,如今有了restful规范,就要遵循。简单来讲,就是多个url对应一个视图,视图中封装了get,post,put,patch,delete等主要方法。相对于FBV来讲更加简单,使用了CBV模式。前端
第一步:总体说restful规范是什么? 第二步:再详细说restful建议 1. https代替http,保证数据传输时安全。 2. 在url中通常要体现api标识,这样看到url就知道他是一个api。 http://www.luffycity.com/api/....(建议,由于他不会存在跨域的问题) http://api.luffycity.com/.... 假设: 前段:https://www.luffycity.com/home 后端:https://www.luffycity.com/api/ 3. 在接口中要体现版本 http://www.luffycity.com/api/v1....(建议,由于他不会存在跨域的问题) 注意:版本还能够放在请求头中 http://www.luffycity.com/api/ accept: ... 4. restful也称为面向资源编程,视网络上的一切都是资源,对资源能够进行操做,因此通常资源都用名词。 http://www.luffycity.com/api/user/ 5. 若是要加入一些筛选条件,能够添加在url中 http://www.luffycity.com/api/user/?page=1&type=9 6. 根据method不一样作不一样操做。 7. 返回给用户状态码 - 200,成功 - 300,301永久 /302临时 - 400,403拒绝 /404找不到 - 500,服务端代码错误 不少公司: def get(self,request,*args,**kwargs): result = {'code':1000,'data':None,'error':None} try: val = int('你好') except Exception as e: result['code'] = 10001 result['error'] = '数据转换错误' return Response(result) 8. 返回值 GET http://www.luffycity.com/api/user/ [ {'id':1,'name':'alex','age':19}, {'id':1,'name':'alex','age':19}, ] POST http://www.luffycity.com/api/user/ {'id':1,'name':'alex','age':19} GET http://www.luffycity.com/api/user/2/ {'id':2,'name':'alex','age':19} PUT http://www.luffycity.com/api/user/2/ {'id':2,'name':'alex','age':19} PATCH https//www.luffycity.com/api/user/2/ {'id':2,'name':'alex','age':19} DELETE https//www.luffycity.com/api/user/2/ 空 9. 操做异常时,要返回错误信息 { error: "Invalid API key" } 10. 对于下一个请求要返回一些接口:Hypermedia AP { 'id':2, 'name':'alex', 'age':19, 'depart': "http://www.luffycity.com/api/user/30/" }
drf是一个基于django开发的组件,本质是一个django的app。drf能够帮咱们快速开发出一个遵循restful规范的程序。
记忆:请求到来以后,先执行视图的dispatch方法。 1. 视图 2. 版本处理 3. 认证 4. 权限 5. 节流(频率限制) 6. 解析器 7. 筛选器 8. 分页 9. 序列化 10. 渲染
-视图类 2个视图基类(APIView,GenericAPIView)和一些其余视图类,帮助咱们完成一些功能。 - 版本 "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning", "ALLOWED_VERSIONS": ['v1', 'v2'], - 认证 经过生成随机字符串保存在后端,前端携带与后端相比较,来实现认证 DEFAULT_AUTHENTICATION_CLASSES = ["类",] BaseAuthentication - 权限 基于BasePermission - 节流 throttle_classes = [AnonRateThrottle,] -解析器,根据用户请求体格式不一样进行数据解析,解析以后放在request.data中。在进行解析时候,drf会读取http请求头 content-type. 若是content-type:x-www-urlencoded,那么drf会根据 & 符号分割的形式去处理请 求体。 user=wang&age=19 若是content-type:application/json,那么drf会根据 json 形式去处理请求体。 {"user":"wang","age":19} -序列化,能够对QuerySet进行序列化,也能够对用户提交的数据进行校验 -筛选器 -分页器 -渲染器,能够帮咱们把json数据渲染到页面上进行友好的展现 Response
app注册python
服务端会根据访问者使用工具的不一样来区分,若是不添加rest_framework,使用postman等工具访问是没有问题的,可是使用浏览器访问会报错,由于会默认找到rest_framework下的静态html文件进行渲染,此时找不到,所以报错。ajax
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework' ]
路由算法
from django.conf.urls import url from django.contrib import admin from api import views urlpatterns = [ url(r'^drf/info/', views.DrfInfoView.as_view()), ]
视图数据库
from rest_framework.views import APIView from rest_framework.response import Response class DrfInfoView(APIView): def get(self,request,*args,**kwargs): data = {"":""} return Response(data)
路由django
from django.conf.urls import url from django.contrib import admin from api import views urlpatterns = [ url(r'^drf/category/$', views.DrfCategoryView.as_view()), #get/post经过此url url(r'^drf/category/(?P<pk>\d+)/$', views.DrfCategoryView.as_view()), #put/patch/delete经过此url ]
视图编程
from api import models from django.forms.models import model_to_dict #序列化,将model转为dict类型 class DrfCategoryView(APIView): def get(self,request,*args,**kwargs): """获取全部文章分类/单个文章分类""" pk = kwargs.get('pk') if not pk: queryset = models.Category.objects.all().values('id','name') data_list = list(queryset) return Response(data_list) else: category_object = models.Category.objects.filter(id=pk).first() data = model_to_dict(category_object) return Response(data) def post(self,request,*args,**kwargs): """增长一条分类信息""" models.Category.objects.create(**request.data) return Response('成功')
https://www.cnblogs.com/wuzhengzheng/p/10411785.htmljson
-序列化时instance=queryset,反序列化data参数写为data=request.data-进行序列化时,会将fields中全部字段进行序列化,反序列化时,也须要提交fields中的全部字段,若是不想提交某些字段,使用read_only参数。-在添加数据(post)请求时,能够新建一个serializer,可能有一些不须要的字段。 source:能够指定字段(name publish.name),能够指定方法。 SerializerMethodField搭配方法使用(get_字段名字) read_only:反序列化时,不传 write_only:序列化时,不显示
路由后端
from django.conf.urls import url from django.contrib import admin from api import views urlpatterns = [ url(r'^new/category/$', views.NewCategoryView.as_view()), url(r'^new/category/(?P<pk>\d+)/$', views.NewCategoryView.as_view()), ]
序列化
# ModelSerializer跟表模型绑定序列化 from app import models class BookSerializer(serializers.ModelSerializer): class Meta: # 指定表模型 model = models.Book # 序列化全部的字段 fields = '__all__' # 只想序列化title和id两个字段 # fields = ['title','id'] # exclude 和 fields不要连用 # excude = ['title] # depth深度,表示链表的深度 #不建议使用:下几层要取得参数不能控制,官方建议不要超过10,我的建议不超过3 # depth = 1 # publish = serializers.CharField(source='publish.name') # authors = serializers.SerializerMethodField() # def get_authors(self, obj): # author_list = obj.authors.all() # author_ser = AuthorSer(instance=author_list, many=True) # return author_ser.data #为书名增长自定义需求 title = serializers.CharField(max_length=6,min_length=3,error_messages={'max_length':'太长了'}) #也有局部钩子函数 def validate_title(self,value): from rest_framework import exceptions print(value) if value.startswith('tmd'): raise exceptions.ValidationError('不能以tmd开头') return value
三个方式
要跨表查看其余表中的字段,或者显示models.CharField(choice=((1,"发布"),(2,"删除"))
class ArticleSerializer(serializers.ModelSerializer): #方式一:设置跨表深度 depth=1 #方式二:指定source category = serializers.CharField(source="category.name") status = serializers.CharField(source="get_status_display") #方式三,使用钩子,和下面get_tags_txt结合使用 tags_txt = serializers.SerializerMethodField(read_only=True) class Meta: model = models.Article fields = ["id","title","summary","content","category","status","tags_txt"] def get_tags_txt(self,obj): #obj为表对象 tag_list = [] for i in obj.tags.all(): tag_list.append({"id":i.id,"name":i.name}) return tag_list
查询
class ArticleView(APIView): def get(self,request,*args,**kwargs): pk = kwargs.get("pk") if not pk: queryset = models.Article.objects.all() ser = ArticleSerializer(instance=queryset,many=True) #查询多个,many=True return Response(ser.data) else: queryset = models.Article.objects.filter(id=pk).first() ser = ArticleSerializer(instance=queryset,many=False) #查询一个,many=False return Response(ser.data)
新增
def post(self,request,*args,**kwargs): ser = ArticleSerializer(data=request.data) if ser.is_valid(): ser.save() return Response("添加成功!") return Response(ser.errors)
添加
def post(self,request,*args,**kwargs): ser = ArticleSerializer(data=request.data) ser_detail = ArticleDetailSerializer(data=request.data) if ser.is_valid() and ser_detail.is_valid(): #前端提交时没有author_id字段,在save时才添加,此处针对连表添加 article_object = ser.save(author_id=1) ser_detail.save(article=article_object) return Response('添加成功') return Response('错误')
修改
def put(self,request,*args,**kwargs): pk = kwargs.get("pk") article_object = models.Article.objects.filter(id=pk).first() ser = ArticleSerializer(instance=article_object,data=request.data) #局部更新添加partial=True,全部要反序列化的字段都不是必填的 if ser.is_valid(): ser.save() return Response(ser.data) return Response(ser.errors)
在本身定义的类中重写create方法
class BookSerializer(serializers.Serializer): ... ... def create(self, validated_data): res = models.Book.objects.create(**validated_data) return res
对应的view.py
class Books(APIView): # 使用继承了Serializers序列化类的对象,反序列化 def post(self,request): response_dic = {'code': 100, 'msg': '添加成功!'} # 实例化产生一个序列化类的对象,data是要反序列化的字典 booker = BookSerializer(data=request.data) if booker.is_valid(): # 清洗经过的数据,经过create方法进行保存 res = booker.create(booker.validated_data) return Response(response_dic)
url访问
https://www.cnblogs.com/article/?category=1
自定义筛选功能MyFilterBackend
from django.shortcuts import render from rest_framework.views import APIView from rest_framework.response import Response from . import models from rest_framework.filters import BaseFilterBackend class MyFilterBackend(BaseFilterBackend): def filter_queryset(self, request, queryset, view): val = request.query_params.get('cagetory') return queryset.filter(category_id=val)
应用
class IndexView(APIView): def get(self, request, *args,**kwargs): # http://www.xx.com/cx/index/?category=1 # models.News.objects.filter(category=1) # http://www.xx.com/cx/index/?category=1 queryset = models.News.objects.all() obj = MyFilterBackend() obj.filter_queryset(request,queryset,self) return Response('...')
视图
#views.py中 from rest_framework.filters import BaseFilterBackend #自定义筛选器 class MyFilterBackend(BaseFilterBackend): def filter_queryset(self, request, queryset, view): val = request.query_params.get("category") return queryset.filter(category=val) #视图 class ArticleView(ListAPIView,CreateAPIView): queryset = models.Article.objects.all() serializer_class = ArticleSerials filter_backends = [MyFilterBackend,]
drf默认集成了几个分页类
from rest_framework.pagination import BasePagination,PageNumberPagination,LimitOffsetPagination,CursorPagination
本身写类继承这些,只须要配置某些参数
class GoodsPagination(PageNumberPagination): """ 分页 """ page_size = 10 page_size_query_param = 'page_size' page_query_param = 'page' max_page_size = 100
url访问
http:127.0.0.1:8000/?page=1
配置settings.py,在配置drf组件都要写在REST_FRAMEWORK字典中
REST_FRAMEWORK = { "PAGE_SIZE":2 }
views中视图函数
def get(self,request,*args,**kwargs): queryset = models.Article.objects.all() # 方式一:仅数据 """ page_object = PageNumberPagination() result = page_object.paginate_queryset(queryset,request,self) ser = PageArticleSerializer(instance=result,many=True) return Response(ser.data) """ # 方式二:数据 + 分页信息 """ page_object = PageNumberPagination() result = page_object.paginate_queryset(queryset, request, self) ser = PageArticleSerializer(instance=result, many=True) return page_object.get_paginated_response(ser.data) """ # 方式三:数据 + 部分分页信息 """ page_object = PageNumberPagination() result = page_object.paginate_queryset(queryset, request, self) ser = PageArticleSerializer(instance=result, many=True) return Response({'count':page_object.page.paginator.count,'result':ser.data}) """ return Response(ser.data)
配置
1.settings中配置 REST_FRAMEWORK = { "DEFAULT_PAGINATION_CLASS":"rest_framework.pagination.PageNumberPagination", #默认使用PagenumberPagination "PAGE_SIZE":2, #每页显示数量 } 2.views.py中 from rest_framework.pagination import PageNumberPagination class ArticleView(ListAPIView,CreateAPIView): queryset = models.Article.objects.all() serializer_class = ArticleSerials pagination_class = PageNumberPagination
url访问
http://127.0.0.1:8000/?limit=4&offset=0
limit多少条 offset后面多少页
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import LimitOffsetPagination from rest_framework import serializers class HulaLimitOffsetPagination(LimitOffsetPagination): max_limit = 2 class PageArticleView(APIView): def get(self,request,*args,**kwargs): queryset = models.Article.objects.all() page_object = HulaLimitOffsetPagination() result = page_object.paginate_queryset(queryset, request, self) ser = PageArticleSerializer(instance=result, many=True) return Response(ser.data)
2个视图基类
APIView是drf中全部视图的基类,继承自django的view类
APIView和view的不一样之处:
传入到视图方法中的是REST framework的Request对象,而不是Django的HttpRequeset对象; 视图方法能够返回REST framework的Response对象,视图会为响应数据设置(render)符合前端要求的格式; 任何APIException异常都会被捕获到,而且处理成合适的响应信息; 在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制。
支持定义的类属性:
authentication_classes 列表或元祖,身份认证类 permissoin_classes 列表或元祖,权限检查类 throttle_classes 列表或元祖,流量控制类
在APIView
中仍以常规的类视图定义方法来实现get() 、post() 或者其余请求方式的方法。
from rest_framework.views import APIView from rest_framework.response import Response # url(r'^students/$', views.StudentsAPIView.as_view()), class StudentsAPIView(APIView): def get(self, request): data_list = Student.objects.all() serializer = StudentModelSerializer(instance=data_list, many=True) return Response(serializer.data)
继承自APIView主要增长了操做序列化器和数据库查询的方法,做用是为下面Mixin扩展类的执行提供方法支持。一般在使用时,可搭配一个或多个Mixin扩展类
支持定义的类属性:
serializer_class 指明视图使用的序列化器 queryset 指明使用的数据查询集 pagination_class 指明分页控制类 filter_backends 指明过滤控制后端
支持的方法:
get_serializer_class(self) 当出现一个视图类中调用多个序列化器时,那么能够经过条件判断在get_serializer_class方法中经过返回不一样的序列化器类名就可让视图方法执行不一样的序列化器对象了 get_serializer(self, args, *kwargs) 返回序列化器对象,主要用来提供给Mixin扩展类使用,若是咱们在视图中想要获取序列化器对象,也能够直接调用此方法。 get_queryset(self) 返回视图使用的查询集,主要用来提供给Mixin扩展类使用,是列表视图与详情视图获取数据的基础,默认返回queryset属性 get_object(self) 返回详情视图所需的模型类数据对象,主要用来提供给Mixin扩展类使用
5个视图扩展类
ListModelMixin 列表视图扩展类,提供list(request, *args, **kwargs)方法快速实现列表视图返回200状态码。 该Mixin的list方法会对数据进行过滤和分页 CreateModelMixin 建立视图扩展类,提供create(request, *args, **kwargs)方法快速实现建立资源的视图,成功返回201状态码。 若是序列化器对前端发送的数据验证失败,返回400错误。 RetrieveModelMixin 详情视图扩展类,提供retrieve(request, *args, **kwargs)方法,能够快速实现返回一个存在的数据对象。 若是存在,返回200, 不然返回404 UpdateModelMixin 更新视图扩展类,提供update(request, *args, **kwargs)方法,能够快速实现更新一个存在的数据对象。 同时也提供partial_update(request, *args, **kwargs)方法,能够实现局部更新。 成功返回200,序列化器校验数据失败时,返回400错误 DestroyModelMixin 删除视图扩展类,提供destroy(request, *args, **kwargs)方法,能够快速实现删除一个存在的数据对象。 成功返回204,不存在返回404。
GenericAPIView的视图子类
CreateAPIView 提供 post 方法 继承自: GenericAPIView、CreateModelMixin ListAPIView 提供 get 方法 继承自:GenericAPIView、ListModelMixin RetrieveAPIView 提供 get 方法 继承自: GenericAPIView、RetrieveModelMixin DestoryAPIView 提供 delete 方法 继承自:GenericAPIView、DestoryModelMixin UpdateAPIView 提供 put 和 patch 方法 继承自:GenericAPIView、UpdateModelMixin RetrieveUpdateAPIView 提供 get、put、patch方法 继承自: GenericAPIView、RetrieveModelMixin、UpdateModelMixin
总体流程
1.发送get请求,会调用ListAPIView中的get方法 2.执行list方法,先去ArticleView中找,找不到,直到找到父类ListModelMixin中的list方法 3.queryset = self.filter_queryset(self.get_queryset())先找ArticleView中的filter_queryset和get_queryset()方法,找不到,直到找到GenericAPIView中有这2中方式,get_queryset()中queryset = self.queryset,从ArticleView中找self.queryset,找不到报错,找到了将其queryset传给此类中的queryset变量,而后将其值返回给filter_querset方法,最后将filter_queryset的返回值赋值给3步中的queryset,在list方法中执行序列化
使用
class TagSer(serializers.ModelSerializer): class Meta: model = models.Tag fields = "__all__" class TagView(ListAPIView,CreateAPIView): queryset = models.Tag.objects.all() serializer_class = TagSer def get_serializer_class(self): #重写GenericAPIView中的方法 # self.request # self.args # self.kwargs if self.request.method == 'GET': return TagSer elif self.request.method == 'POST': return OtherTagSer def perform_create(self,serializer): serializer.save(author=1)
使用思路
在视图类中配置 versioning_class =注意这是单数形式,只能配置一个类 实现 determine_version 方法 全局配置 DEFAULT_VERSION ALLOWED_VERSIONS VERSION_PARAM 在 initial 中首先执行 determine_version,它里面会生成获取版本的对象以及版本。 获取版本:request.version 获取处理版本的对象:request.versioning_scheme 反向生成 url :url = request.versioning_scheme.reverse(viewname='<url 的别名>', request=request)
使用(局部)
url中写version
url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'),
视图中应用
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.request import Request from rest_framework.versioning import URLPathVersioning class OrderView(APIView): versioning_class = URLPathVersioning def get(self,request,*args,**kwargs): print(request.version) print(request.versioning_scheme) return Response('...') def post(self,request,*args,**kwargs): return Response('post')
settings中配置
REST_FRAMEWORK = { "PAGE_SIZE":2, "DEFAULT_PAGINATION_CLASS":"rest_framework.pagination.PageNumberPagination", "ALLOWED_VERSIONS":['v1','v2'], #容许的版本 'VERSION_PARAM':'version' #默认参数 }
使用(全局)
url中写version
url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'), url(r'^(?P<version>\w+)/users/$', users_list, name='users-list'),
视图中应用
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.request import Request from rest_framework.versioning import URLPathVersioning class OrderView(APIView): def get(self,request,*args,**kwargs): print(request.version) print(request.versioning_scheme) return Response('...') def post(self,request,*args,**kwargs): return Response('post')
settings配置
REST_FRAMEWORK = { "PAGE_SIZE":2, "DEFAULT_PAGINATION_CLASS":"rest_framework.pagination.PageNumberPagination", "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning", # "ALLOWED_VERSIONS":['v1','v2'], 'VERSION_PARAM':'version' }
先来登陆功能,登陆成功后将随机字符串加入Userinfo表的token字段中
表
class UserInfo(models.Model): """ 用户表 """ username = models.CharField(verbose_name='用户名',max_length=32) password = models.CharField(verbose_name='密码',max_length=64) token = models.CharField(verbose_name='token',max_length=64,null=True,blank=True)
LoginView登陆视图的实现
from rest_framework.views import APIView from rest_framework.response import Response from api import models import uuid class LoginView(APIView): """ 登陆接口 """ def post(self,request,*args,**kwargs): user_obj = models.UserInfo.objects.filter(**request.data).first() if not user_obj: return Response("登陆失败!") random_string = str(uuid.uuid4()) # 登陆成功后将token添加到数据库,且返回给前端 user_obj.token = random_string user_obj.save() return Response(random_string)
在以后写的其余视图函数中,某些视图函数须要验证用户是否登陆,就用到drf提供的BaseAuthentication认证功能。
先来单独写认证组件,须要用到的视图函数直接调用便可
from rest_framework.authentication import BaseAuthentication from api import models class LuffyAuthentication(BaseAuthentication): def authenticate(self, request): token = request.query_params.get("token") if not token: return (None,None) user_obj = models.UserInfo.objects.filter(token=token).first() if user_obj: return (user_obj,token) #参数一给request.user,参数二给request.auth return (None,None)
须要用到认证功能的函数以下,调用方式以下:
from rest_framework.views import APIView from rest_framework.response import Response from api.extension.auth import LuffyAuthentication class CommentView(APIView): #authentication_classes = [] 在settings中设置了全局认证,但此处不需,只需设置为空列表便可 authentication_classes = [LuffyAuthentication,] def get(self,request,*args,**kwargs): print(request.user) #request.user获得user object对象,只有request.user才会调用认证 print(request.auth) #获得token值 return Response("获取全部评论") def post(self,request,*args,**kwargs): if request.user: pass
若是你有100个视图想要用认证组件,就须要在全局settings.py中设置了
REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES":["api.extension.auth.LuffyAuthentication",] }
源码分析(从认证功能函数开始)
1.当有请求发来时,会先执行APIView中的dispath方法,dispath方法中会执行initialize_request(request, *args, **kwargs),进行老的request的封装。在封装过程当中authenticators=self.get_authenticators(),此时执行get_authenticators(),进去此方法会看到return [auth() for auth in self.authentication_classes],由于本身写的类中有authentication_class变量,此时authenticators=[LuffyAuthentication(),] 2. 当执行到get函数时,也是先进APIView的dispatch()方法,而后进入request的封装函数中,查询def user(self)方法,找到其中的self._authenticate()方法并执行,能够看到 user_auth_tuple = authenticator.authenticate(self),执行本身定义组件中的authenticate方法,返回(a,b)元组,而后self.user, self.auth = 此元组,从而获得request.user等于值1,request.auth等于值2
源码大致流程
当用户发来请求时,找到认证的全部类并实例化成为对象列表,而后将对象列表封装到新的request对象中。 之后在视同中调用request.user 在内部会循环认证的对象列表,并执行每一个对象的authenticate方法,该方法用于认证,他会返回两个值分别会赋值给 request.user/request.auth
jwt的实现原理: - 用户登陆成功以后,会给前端返回一段token。 - token是由.分割的三段组成。 - 第一段:类型和算法信心 - 第二段:用户信息+超时时间 - 第三段:hs256(前两段拼接)加密 + base64url - 之后前端再次发来信息时 - 超时验证 - token合法性校验 优点: - token只在前端保存,后端只负责校验。 - 内部集成了超时时间,后端能够根据时间进行校验是否超时。 - 因为内部存在hash256加密,因此用户不能够修改token,只要一修改就认证失败。
app中注册 rest_framework_jwt
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'api.apps.ApiConfig', 'rest_framework', 'rest_framework_jwt' ]
登陆视图实现生成随机字符串
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework_jwt.settings import api_settings from api import models from rest_framework_jwt.views import VerifyJSONWebToken from rest_framework.throttling import AnonRateThrottle,BaseThrottle class LoginView(APIView): authentication_classes = [] def post(self,request,*args,**kwargs): user = models.UserInfo.objects.filter(username=request.data.get("username"),password=request.data.get("password")).first() if not user: return Response({"code":10001,"error":"用户名或密码错误"}) # jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER payload = jwt_payload_handler(user) # jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER token = jwt_encode_handler(payload) return Response({"code":10000,"data":token})
认证视图
import jwt from rest_framework import exceptions from rest_framework.authentication import BaseAuthentication from rest_framework_jwt.settings import api_settings from api import models class HulaQueryParamAuthentication(BaseAuthentication): def authenticate(self, request): """ # raise Exception(), 不在继续往下执行,直接返回给用户。 # return None ,本次认证完成,执行下一个认证 # return ('x',"x"),认证成功,不须要再继续执行其余认证了,继续日后权限、节流、视图函数 """ token = request.query_params.get('token') if not token: raise exceptions.AuthenticationFailed({'code':10002,'error':"登陆成功以后才能操做"}) jwt_decode_handler = api_settings.JWT_DECODE_HANDLER try: payload = jwt_decode_handler(token) except jwt.ExpiredSignature: raise exceptions.AuthenticationFailed({'code':10003,'error':"token已过时"}) except jwt.DecodeError: raise exceptions.AuthenticationFailed({'code':10004,'error':"token格式错误"}) except jwt.InvalidTokenError: raise exceptions.AuthenticationFailed({'code':10005,'error':"认证失败"}) jwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLER username = jwt_get_username_from_payload(payload) user_object = models.UserInfo.objects.filter(username=username).first() return (user_object,token)
settings配置(全局配置)
REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES":["api.extension.auth.HulaQueryParamAuthentication"], }
若是不想使用此认证,在视图函数中添加如下代码
def get_authenticators(self): if self.request.method == "GET": return [] elif self.request.method == "POST": return super().get_authenticators()
要用权限须要drf中的BasePermission类,先来自定义权限组件
from rest_framework.permissions import BasePermission from rest_framework import exceptions class MyPermission(BasePermission): message = {'code': 10001, 'error': '你没权限'} #has_permission返回false时会抛出此异常 def has_permission(self, request, view): #针对全部请求 if request.user: return True return False def has_object_permission(self, request, view, obj): #针对有pk值的请求(例RetrieveAPIView) return False
视图函数中使用上定义的权限组件
from rest_framework.views import APIView from rest_framework.response import Response from api.extension.permission import LuffyPermission class CommentView(APIView): permission_classes = [] #若是在settings设置了全局权限,但此视图不想使用 permission_classes = [LuffyPermission,] def get(self,request,*args,**kwargs): return Response("获取全部评论") def post(self,request,*args,**kwargs): if request.user: pass
settings配置全局权限
REST_FRAMEWORK = { DEFAULT_PERMISSION_CLASSES:"api.extension.permission.LuffyPermission" }
源码分析
class APIView(View): permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES def dispatch(self, request, *args, **kwargs): #1执行dispath方法 #2 封装request对象 self.initial(request, *args, **kwargs) 经过反射执行视图中的方法 def initial(self, request, *args, **kwargs): # 权限判断 self.check_permissions(request) def check_permissions(self, request): # [对象,对象,] for permission in self.get_permissions(): if not permission.has_permission(request, self): self.permission_denied(request, message=getattr(permission, 'message', None)) def permission_denied(self, request, message=None): if request.authenticators and not request.successful_authenticator: raise exceptions.NotAuthenticated() raise exceptions.PermissionDenied(detail=message) def get_permissions(self): return [permission() for permission in self.permission_classes] class UserView(APIView): permission_classes = [MyPermission, ] def get(self,request,*args,**kwargs): return Response('user')
源码分析
1.请求过来,先走APIView中的dispatch方法,执行self.initialize_request()方法进行老的request的封装,而后执行initial方法,进行认证和权限处理: self.perform_authentication(request) self.check_permissions(request) self.check_throttles(request) 2.进入check_permissions方法:for循环遍历本身定义的permission_classes,检查每一个对象的has_permission()方法,若是返回false,抛出异常,返回true,继续经过反射执行dispatch方法中的请求分发 for permission in self.get_permissions(): if not permission.has_permission(request, self): self.permission_denied( request, message=getattr(permission, 'message', None) )
频率限制在认证、权限以后
知识点
{ throttle_anon_1.1.1.1:[100121340,], 1.1.1.2:[100121251,100120450,] } 限制:60s能访问3次 来访问时: 1.获取当前时间 100121280 2.100121280-60 = 100121220,小于100121220全部记录删除 3.判断1分钟之内已经访问多少次了? 4 4.没法访问 停一会 来访问时: 1.获取当前时间 100121340 2.100121340-60 = 100121280,小于100121280全部记录删除 3.判断1分钟之内已经访问多少次了? 0 4.能够访问
如何实现频率限制
匿名用户:用ip做为惟一标记(缺点:若是用户使用代理ip,没法作到真正的限制) 登陆用户:用用户名或者id做为标识
具体实现
首先,django会在缓存中生成以下数据结构,用来存储用户访问的时间戳 throttle_匿名标识(anon)_用户ip:[时间戳1,时间戳2...] 每次用户登陆时: 1.获取当前时间戳 2.当前时间戳-60(如3/60s,一分钟内3次),循环数据结构中时间戳B,将获得值A和B比较,B<A,则删除B 3.判断数据结构中列表的个数,若是小于3,则能够访问
使用
#settings配置: REST_FRAMEWORK={ "DEFAULT_THROTTLE_RATES":{ "anon":'3/m' #一分钟3次, 3/h 3/d } } #views函数中使用 from rest_framework.throttling import AnonRateThrottle class ArticleView(APIView): throttle_classes = [AnonRateThrottle,]
源码剖析
请求发来,先执行dispatch方法,从dispatch方法中进入inital函数。前后执行到check_throttles()方法,在此看到,会执行对象的allow_request()方法 def initial(self, request, *args, **kwargs): self.perform_authentication(request) self.check_permissions(request) self.check_throttles(request) def check_throttles(self, request): throttle_durations = [] for throttle in self.get_throttles(): if not throttle.allow_request(request, self): throttle_durations.append(throttle.wait()) def allow_request(self, request, view): if self.rate is None: return True # 获取请求用户的ip self.key = self.get_cache_key(request, view) if self.key is None: return True # 根据ip获取全部访问记录,获得的一个列表 self.history = self.cache.get(self.key, []) self.now = self.timer() # Drop any requests from the history which have now passed the # throttle duration while self.history and self.history[-1] <= self.now - self.duration: self.history.pop() if len(self.history) >= self.num_requests: #num_requests本身规定的访问次数 return self.throttle_failure() return self.throttle_success()
总结
# 如何实现的评率限制 - 匿名用户,用IP做为用户惟一标记,但若是用户换代理IP,没法作到真正的限制。 - 登陆用户,用用户名或用户ID作标识。 具体实现: 在django的缓存中 = { throttle_anon_1.1.1.1:[100121340,], 1.1.1.2:[100121251,100120450,] } 限制:60s能访问3次 来访问时: 1.获取当前时间 100121280 2.100121280-60 = 100121220,小于100121220全部记录删除 3.判断1分钟之内已经访问多少次了? 4 4.没法访问 停一会 来访问时: 1.获取当前时间 100121340 2.100121340-60 = 100121280,小于100121280全部记录删除 3.判断1分钟之内已经访问多少次了? 0 4.能够访问
组件参考:https://www.jianshu.com/p/c16f8786e9f7
因为浏览器具备“同源策略”的限制,致使 `发送ajax请求` + `跨域` 存在没法获取数据。 若是在同一个域下发送ajax请求,浏览器的同源策略不会阻止。 若是在不一样域下发送ajax,浏览器的同源策略会阻止。
本质在数据返回值设置响应头 from django.shortcuts import render,HttpResponse def json(request): response = HttpResponse("JSONasdfasdf") response['Access-Control-Allow-Origin'] = "*" return response
简单请求,发一次请求
设置响应头就能够解决 from django.shortcuts import render,HttpResponse def json(request): response = HttpResponse("JSONasdfasdf") response['Access-Control-Allow-Origin'] = "*" return response
复杂请求,发两次请求
@csrf_exempt def put_json(request): response = HttpResponse("JSON复杂请求") if request.method == 'OPTIONS': # 处理预检 response['Access-Control-Allow-Origin'] = "*" response['Access-Control-Allow-Methods'] = "PUT" return response elif request.method == "PUT": return response
条件: 一、请求方式:HEAD、GET、POST 二、请求头信息: Accept Accept-Language Content-Language Last-Event-ID Content-Type 对应的值是如下三个中的任意一个 application/x-www-form-urlencoded multipart/form-data text/plain 注意:同时知足以上两个条件时,则是简单请求,不然为复杂请求
1. 因为浏览器具备“同源策略”的限制,因此在浏览器上跨域发送Ajax请求时,会被浏览器阻止。 2. 解决跨域 - 不跨域 - CORS(跨站资源共享,本质是设置响应头来解决)。 - 简单请求:发送一次请求 - 复杂请求:发送两次请求