1.简介
drf 是django rest framewok的简称,主要用于前后端分离项目,用于前端调取接口,后端返回json
drf是一个基于django开发的组件,本质是一个django的app。
drf可以办我们快速开发出一个遵循restful规范的程序。
简单说 只是一个项目而已,然后调取
涉及restful规范 是约束接口的规则
1.给别人提供一个URL,根据URL请求方式的不同,做不同操作。 get,获取 post,增加 put,全部更新 patch,局部更新 delete,删除 2.数据传输基于json格式。 3. 建议用https代替http 4. 在URL中体现api,添加api标识 https://www.cnblogs.com/xwgblog/p/11812244.html # 错误 https://www.cnblogs.com/api/xwgblog/p/11812244.html # 正确 https://api.cnblogs.com/xwgblog/p/11812244.html # 正确 建议:https://www.cnblogs.com/api/... 5. 在URL中要体现版本 https://www.cnblogs.com/api/v1/userinfo/ https://www.cnblogs.com/api/v2/userinfo/ 6. 一般情况下对于api接口,用名词不用动词。 https://www.cnblogs.com/api/v1/userinfo/ 7. 如果有条件的话,在URL后面进行传递。 https://www.cnblogs.com/api/v1/userinfo/?page=1&category=2 8. 返回给用户状态码(code) 9. 对下一个请求返回一些其他接口
当然根据Django也可以实现遵循restful规范的接口开发:
其中两种视图模式
- FBV,可以实现比较麻烦。
- CBV,相比较简答根据method做的了不同的区分。
drf组件的功能
解析器,解析请求体中的数据,将其变成我们想要的格式。request.data request.query_params.get('article')
在进行解析时候,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进行序列化,也可以对用户提交的数据进行校验。
视图,继承APIView(在内部apiview继承了django的View)
渲染器,可以帮我们把json数据渲染到页面上进行友好的展示。(内部会根据请求设备不同做不同的
展示)分页,筛选
2.安装
pip3 install djangorestframework
3.使用
3.1注册
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework' #添加项目 ]
3.2路由
from django.conf.urls import url from django.contrib import admin from api import views urlpatterns = [ url(r'^drf/info/', views.DrfInfoView.as_view()), ]
3.3视图
注意 APIView中没有JsonResponse
使用要使用Response
from rest_framework.views import APIView from rest_framework.response import Response class DrfInfoView(APIView): def get(self,request,*args,**kwargs): data = [ {'id': 1, 'title': '震惊了...你居然吃...', 'content': '...'}, {'id': 2, 'title': '震惊了...他居然吃...', 'content': '...'}, ] return Response(data)
4.基于接口的增删改查low版本
from django.urls import path,re_path from api import views urlpatterns = [ re_path('drf/category$', views.DrfCategoryView.as_view()), re_path('drf/category/(?P<pk>\d+)/$', views.DrfCategoryView.as_view()), ]
from api import models from django.forms.models import model_to_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('成功') def delete(self,request,*args,**kwargs): """删除""" pk = kwargs.get('pk') models.Category.objects.filter(id=pk).delete() return Response('删除成功') def put(self,request,*args,**kwargs): """更新""" pk = kwargs.get('pk') models.Category.objects.filter(id=pk).update(**request.data) return Response('更新成功')
from django.db import models class Category(models.Model): """ 文章分类 """ name = models.CharField(verbose_name='分类',max_length=32) class Article(models.Model): """ 文章表 """ title = models.CharField(verbose_name='标题',max_length=32) summary = models.CharField(verbose_name='简介',max_length=255) content = models.TextField(verbose_name='文章内容') cates = models.ForeignKey(to=Category,blank=True,null=True,on_delete=models.CASCADE)
5.序列化
(可以直接看6)
drf的 serializers帮助我们提供了
- 数据校验
- 序列化
url(r'^new/category/$', views.NewCategoryView.as_view()), url(r'^new/category/(?P<pk>\d+)/$', views.NewCategoryView.as_view()),
from rest_framework import serializers from rest_framework.views import APIView class NewCategorySerializer(serializers.ModelSerializer): class Meta: model = models.Category # fields = "__all__" fields = ['id','name'] class NewCategoryView(APIView): def get(self,request,*args,**kwargs): pk = kwargs.get('pk') if not pk: queryset = models.Category.objects.all() ser = NewCategorySerializer(instance=queryset,many=True) return Response(ser.data) else: model_object = models.Category.objects.filter(id=pk).first() ser = NewCategorySerializer(instance=model_object, many=False) return Response(ser.data) def post(self,request,*args,**kwargs): ser = NewCategorySerializer(data=request.data) if ser.is_valid(): ser.save() return Response(ser.data) return Response(ser.errors) def put(self,request,*args,**kwargs): pk = kwargs.get('pk') category_object = models.Category.objects.filter(id=pk).first() ser = NewCategorySerializer(instance=category_object,data=request.data) if ser.is_valid(): ser.save() return Response(ser.data) return Response(ser.errors) def delete(self,request,*args,**kwargs): pk = kwargs.get('pk') models.Category.objects.filter(id=pk).delete() return Response('删除成功')
6.跨表展示,跨字典展示,一对多
from django.db import models class Category(models.Model): """ 文章分类 """ name = models.CharField(verbose_name='分类',max_length=32) class Article(models.Model): """ 文章表 """ status_choices = ( (1,'发布'), (2,'删除'), ) status = models.IntegerField('状态',choices=status_choices,default=1) #字典 title = models.CharField('标题',max_length=32) summary = models.CharField('简介',max_length=255) content = models.TextField('文章内容') cates = models.ForeignKey(to=Category,on_delete=models.CASCADE) #跨表
urlpatterns = [ re_path('drf/text/$', views.DrfNewText.as_view()), re_path('drf/text/(?P<pk>\d+)/$', views.DrfNewText.as_view()), ]
from rest_framework.views import APIView from rest_framework.response import Response from api import models from api.serializer import NewText class DrfNewText(APIView): def get(self,request,*args,**kwargs): pk = kwargs.get('pk') if not pk: obj = models.Article.objects.all() ser = NewText(instance=obj,many=True) return Response(ser.data) else: obj = models.Article.objects.filter(id=pk).first() ser = NewText(instance=obj,many=False) return Response(ser.data) def post(self,request,*args,**kwargs): ser = NewText(data=request.data) if ser.is_valid(): ser.save() return Response(ser.data) return Response(ser.errors) def put(self, request, *args, **kwargs): pk = kwargs.get('pk') obj = models.Article.objects.filter(id=pk).first() ser = NewText(instance=obj, data=request.data) if ser.is_valid(): ser.save() return Response(ser.data) return Response(ser.errors) def delete(self, request, *args, **kwargs): pk = kwargs.get('pk') models.Article.objects.filter(id=pk).delete() return Response('删除成功')
from rest_framework import serializers from api import models class NewText(serializers.ModelSerializer): #跨表查询出name,名字与数据库字段名相同时,会覆盖,需要加read_only=True cates_l = serializers.CharField(source='cates.name', required=False) status_l = serializers.CharField(source='get_status_display',required=False) # x1 = serializers.SerializerMethodField() class Meta: model = models.Article # fields = '__all__' fields = ['id','title','summary','content','cates','cates_l','status','status_l'] ##方法定义x1 设置get_x1方法 # def get_x1(self,obj): # return obj.cates.name
postman请求
只需要提交数据库有的字段
{ "title": "今天1w23", "summary": "明天", "content": "后天", "cates": 1, "status": 2 }
7.多对多
可写两个serializers ,显示和提交分开
from django.db import models class Category(models.Model): """ 文章分类 """ name = models.CharField(verbose_name='分类',max_length=32) class Article(models.Model): """ 文章表 """ status_choices = ( (1,'发布'), (2,'删除'), ) status = models.IntegerField('状态',choices=status_choices,default=1) title = models.CharField('标题',max_length=32) summary = models.CharField('简介',max_length=255) content = models.TextField('文章内容') cates = models.ForeignKey(to=Category,on_delete=models.CASCADE) tag = models.ManyToManyField(verbose_name='标签', to='Tag', blank=True) class Tag(models.Model): """标签""" title = models.CharField(verbose_name='标签',max_length=32)
from rest_framework import serializers from api import models class NewText(serializers.ModelSerializer): #跨表查询出name,并替换了fields的cates cates = serializers.CharField(source='cates.name', required=False) status = serializers.CharField(source='get_status_display',required=False) tag = serializers.SerializerMethodField() class Meta: model = models.Article fields = ['id','title','summary','content','cates','status','tag'] def get_tag(self,obj): query_set = obj.tag.all() return [({'id':obj.id,'title':obj.title} )for obj in query_set] class Newpost(serializers.ModelSerializer): class Meta: model =models.Article fields = '__all__'
from rest_framework.views import APIView from rest_framework.response import Response from api import models from api.serializer import NewText,Newpost class DrfNewText(APIView): def get(self,request,*args,**kwargs): pk = kwargs.get('pk') if not pk: obj = models.Article.objects.all() ser = NewText(instance=obj,many=True) return Response(ser.data) else: obj = models.Article.objects.filter(id=pk).first() ser = NewText(instance=obj,many=False) return Response(ser.data) def post(self,request,*args,**kwargs): ser = Newpost(data=request.data) if ser.is_valid(): ser.save() return Response(ser.data) return Response(ser.errors) def put(self, request, *args, **kwargs): pk = kwargs.get('pk') obj = models.Article.objects.filter(id=pk).first() ser = Newpost(instance=obj, data=request.data) if ser.is_valid(): ser.save() return Response(ser.data) return Response(ser.errors) def delete(self, request, *args, **kwargs): pk = kwargs.get('pk') models.Article.objects.filter(id=pk).delete() return Response('删除成功')
8.PageNumberPagination 分页
REST_FRAMEWORK = { 'PAGE_SIZE':3, # 'DEFAULT_PAGINATION_CLASS':"rest_framework.pagination.PageNumberPagination" } #指定分页方式
from rest_framework.pagination import PageNumberPagination from rest_framework import serializers from api import models class PageArticleSerializer(serializers.ModelSerializer): class Meta: model = models.Article fields = "__all__" class Page(APIView): def get(self,request,*args,**kwargs): queryset = models.Article.objects.all() #方法一,仅数据 #分页对象 page_object = PageNumberPagination() # 调用 分页对象.paginate_queryset方法进行分页,得到的结果是分页之后的数据 # result就是分完页的一部分数据 result = page_object.paginate_queryset(queryset, request, self) # 序列化分页之后的数据 ser = PageArticleSerializer(instance=result, many=True) return Response(ser.data)
# 方式二:数据 + 分页信息 return page_object.get_paginated_response(ser.data) #只有这行不一样 #方式三: 自定义返回数据 return Response({'count': page_object.page.paginator.count, 'result': ser.data})
9.LimitOffsetPagination 分页
from rest_framework.pagination import LimitOffsetPagination from rest_framework import serializers class PageArticleSerializer(serializers.ModelSerializer): class Meta: model = models.Article fields = "__all__" class HulaLimitOffsetPagination(LimitOffsetPagination): max_limit = 2 #限定最大条数 class Page(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) ### http://127.0.0.1:8000/page/article/?offset=0&limit=7
10.扩展用法
调取内部方法设置分页
REST_FRAMEWORK = { 'PAGE_SIZE':3, 'DEFAULT_PAGINATION_CLASS':"rest_framework.pagination.PageNumberPagination" }
from rest_framework.generics import ListAPIView from rest_framework import serializers from api import models class PageViewArticleSerializer(serializers.ModelSerializer): class Meta: model = models.Article fields = "__all__" class PageViewArticleView(ListAPIView): queryset = models.Article.objects.all() serializer_class = PageViewArticleSerializer #http://127.0.0.1:8000/page/article/?page=1
11.呼拉圈接口设置
1.设置表结构
不会经常变化的值放在内存:choices形式,避免跨表性能低。 如标题
分表:如果表中列太多/大量内容可以选择水平分表 如内容
from django.db import models class UserInfo(models.Model): """ 用户表 """ username = models.CharField(verbose_name='用户名',max_length=32) password = models.CharField(verbose_name='密码',max_length=64) class Article(models.Model): """ 文章表 """ category_choices = ( (1,'咨询'), (2,'公司动态'), (3,'分享'), (4,'答疑'), (5,'其他'), ) category = models.IntegerField(verbose_name='分类',choices=category_choices) title = models.CharField(verbose_name='标题',max_length=32) image = models.CharField(verbose_name='图片路径',max_length=128) # /media/upload/.... summary = models.CharField(verbose_name='简介',max_length=255) comment_count = models.IntegerField(verbose_name='评论数',default=0) read_count = models.IntegerField(verbose_name='浏览数',default=0) author = models.ForeignKey(verbose_name='作者',to='UserInfo') date = models.DateTimeField(verbose_name='创建时间',auto_now_add=True) class ArticleDetail(models.Model): article = models.OneToOneField(verbose_name='文章表',to='Article') content = models.TextField(verbose_name='内容') class Comment(models.Model): """ 评论表 """ article = models.ForeignKey(verbose_name='文章',to='Article') content = models.TextField(verbose_name='评论') user = models.ForeignKey(verbose_name='评论者',to='UserInfo') # parent = models.ForeignKey(verbose_name='回复',to='self', null=True,blank=True)
2.url
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^article/', views.ArticleView.as_view()), ]
3.功能实现
1.增加文章(可以不写)一次增加两个表中的数据:
from . import models from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import serializers class ArticleSerializer(serializers.ModelSerializer): class Meta: model = models.Article # fields = '__all__' exclude = ['author',] class ArticleDetaSerializer(serializers.ModelSerializer): class Meta: model = models.ArticleDetail # fields = '__all__' exclude = ['article',] class ArticleView(APIView): """文章的视图类""" def get(self,request,*args,**kwargs): """获取文章""" pass def post(self,request,*args,**kwargs): """新增文章""" ser = ArticleSerializer(data=request.data) ser_detail = ArticleDetaSerializer(data=request.data) if ser.is_valid() and ser_detail.is_valid(): #增加文章 article_obj = ser.save(author_id=1) #从session取出用户的ID存在数据库,这里没写,现编一个 ser_detail.save(article=article_obj) return Response(ser.data) return Response("错误")
{"category":"2","title":"梦啊","image":"www.zhuimengnan.com","summary":"梦在哪啊你走的那么决然","content":"人生路远,不是不爱了,是想给自己一条退路"}
2.get获取文章列表
class ArticlelistSerializer(serializers.ModelSerializer): class Meta: model = models.Article fields = '__all__' class ArticleView(APIView): """文章的视图类""" def post(self,request,*args,**kwargs): pass #看上边 这里不写了 def get(self,request,*args,**kwargs): """获取文章列表""" #分页 queryset = models.Article.objects.all().order_by('-date') page = PageNumberPagination() result = page.paginate_queryset(queryset,request,self) #序列化 ser = ArticlelistSerializer(instance=result,many=True) return Response(ser.data)
3.文章详细
#文章详细钩子 class PageArticleSerializer(serializers.ModelSerializer): content = serializers.CharField(source='articledetail.content') author = serializers.CharField(source='author.username') category =serializers.CharField(source='get_category_display') date =serializers.SerializerMethodField() class Meta: model = models.Article fields = '__all__' def get_date(self,obj): return obj.date.strftime("%Y-%m-%d %H:%M:%S") class ArticleView(APIView): """文章的视图类""" def get(self,request,*args,**kwargs): """获取文章列表""" pk = kwargs.get('pk') if not pk: #分页 queryset = models.Article.objects.all().order_by('-date') page = PageNumberPagination() result = page.paginate_queryset(queryset,request,self) #序列化 ser = ArticlelistSerializer(instance=result,many=True) return Response(ser.data) #文章详细 article_obj = models.Article.objects.filter(id=pk).first() ser =PageArticleSerializer(instance=article_obj,many=False) return Response(ser.data)
4.评论列表
查看评论列表
访问时:http://127.0.0.1:8000/comment/?article=2
http://127.0.0.1:8000/comment/ { article:1, content:'xxx' }
url(r'^comment/$', views.CommentView.as_view()),
class CommentView(APIView): """评论接口""" def get(self,request): #取得的是url传过来的参数 article_id = request.query_params.get('article') #return self._request.GET queryset = models.Comment.objects.filter(article_id=article_id) ser = CommentSerializer(instance=queryset,many=True) return Response(ser.data) def post(self,request,*args,**kwargs): print(request.data) ser = PostCommentSerializer(data=request.data) if ser.is_valid(): ser.save(user_id=2) return Response('sucess') return Response(ser.errors)
5.总结
from django.db import models class UserInfo(models.Model): """ 用户表 """ username = models.CharField(verbose_name='用户名',max_length=32) password = models.CharField(verbose_name='密码',max_length=64) class Article(models.Model): """ 文章表 """ category_choices = ( (1,'咨询'), (2,'公司动态'), (3,'分享'), (4,'答疑'), (5,'其他'), ) category = models.IntegerField(verbose_name='分类',choices=category_choices) title = models.CharField(verbose_name='标题',max_length=32) image = models.CharField(verbose_name='图片路径',max_length=128) # /media/upload/.... summary = models.CharField(verbose_name='简介',max_length=255) comment_count = models.IntegerField(verbose_name='评论数',default=0) read_count = models.IntegerField(verbose_name='浏览数',default=0) author = models.ForeignKey(verbose_name='作者',to='UserInfo') date = models.DateTimeField(verbose_name='创建时间',auto_now_add=True) class ArticleDetail(models.Model): article = models.OneToOneField(verbose_name='文章表',to='Article') content = models.TextField(verbose_name='内容') class Comment(models.Model): """ 评论表 """ article = models.ForeignKey(verbose_name='文章',to='Article') content = models.TextField(verbose_name='评论') user = models.ForeignKey(verbose_name='评论者',to='UserInfo') # parent = models.ForeignKey(verbose_name='回复',to='self', null=True,blank=True)
from django.conf.urls import url from django.contrib import admin from api import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^article/$', views.ArticleView.as_view()), url(r'^article/(?P<pk>\d+)/$', views.ArticleView.as_view()), url(r'^comment/$', views.CommentView.as_view()), ]
from . import models from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import serializers from rest_framework.pagination import PageNumberPagination class ArticleSerializer(serializers.ModelSerializer): class Meta: model = models.Article # fields = '__all__' exclude = ['author',] class ArticleDetaSerializer(serializers.ModelSerializer): class Meta: model = models.ArticleDetail # fields = '__all__' exclude = ['article',] class ArticlelistSerializer(serializers.ModelSerializer): class Meta: model = models.Article fields = '__all__' class PageArticleSerializer(serializers.ModelSerializer): content = serializers.CharField(source='articledetail.content') author = serializers.CharField(source='author.username') category =serializers.CharField(source='get_category_display') date =serializers.SerializerMethodField() class Meta: model = models.Article fields = '__all__' def get_date(self,obj): print(obj) return obj.date.strftime("%Y-%m-%d %H:%M:%S") class ArticleView(APIView): """文章的视图类""" def post(self,request,*args,**kwargs): """新增文章""" ser = ArticleSerializer(data=request.data) ser_detail = ArticleDetaSerializer(data=request.data) if ser.is_valid() and ser_detail.is_valid(): #增加文章 article_obj = ser.save(author_id=1) #从session取出用户的ID存在数据库,这里没写,现编一个 ser_detail.save(article=article_obj) return Response(ser.data) return Response("错误") def get(self,request,*args,**kwargs): """获取文章列表""" pk = kwargs.get('pk') if not pk: #分页 condition = {} category = request.query_params.get('category') if category: condition['category'] = category queryset = models.Article.objects.filter(**condition).order_by('-date') # queryset = models.Article.objects.all().order_by('-date') page = PageNumberPagination() result = page.paginate_queryset(queryset,request,self) #序列化 ser = ArticlelistSerializer(instance=result,many=True) return Response(ser.data) article_obj = models.Article.objects.filter(id=pk).first() ser =PageArticleSerializer(instance=article_obj,many=False) return Response(ser.data) class CommentSerializer(serializers.ModelSerializer): class Meta: model = models.Comment fields = '__all__' class PostCommentSerializer(serializers.ModelSerializer): class Meta: model = models.Comment exclude =['user',] class CommentView(APIView): """评论接口""" def get(self,request): #取得的是url传过来的参数 article_id = request.query_params.get('article') #return self._request.GET queryset = models.Comment.objects.filter(article_id=article_id) ser = CommentSerializer(instance=queryset,many=True) return Response(ser.data) def post(self,request,*args,**kwargs): print(request.data) ser = PostCommentSerializer(data=request.data) if ser.is_valid(): ser.save(user_id=2) return Response('sucess') return Response(ser.errors)
REST_FRAMEWORK = { 'PAGE_SIZE':2, 'DEFAULT_PAGINATION_CLASS':"rest_framework.pagination.PageNumberPagination" }
12. 筛选
案例:在文章列表时候,添加筛选功能。
全部:http://127.0.0.1:8000/article/ 筛选:http://127.0.0.1:8000/article/?category=2
class ArticleView(APIView): """ 文章视图类 """ def get(self,request,*args,**kwargs): """ 获取文章列表 """ pk = kwargs.get('pk') if not pk: condition = {} category = request.query_params.get('category') if category: condition['category'] = category queryset = models.Article.objects.filter(**condition).order_by('-date') pager = PageNumberPagination() result = pager.paginate_queryset(queryset,request,self) ser = ArticleListSerializer(instance=result,many=True) return Response(ser.data) article_object = models.Article.objects.filter(id=pk).first() ser = PageArticleSerializer(instance=article_object,many=False) return Response(ser.data)
13.drf的组件:内置筛选
from rest_framework.views import APIView from rest_framework.response import Response from . import models from rest_framework.filters import BaseFilterBackend from rest_framework.pagination import PageNumberPagination class NewFilterBackend(BaseFilterBackend): def filter_queryset(self, request, queryset, view): val = request.query_params.get('cagetory') return queryset.filter(category_id=val) class NewSerializers(serializers.ModelSerializer): class Meta: model = models.Tag fields = "__all__" class NewView(ListAPIView): queryset = models.news.objects.all() filter_backends = [NewFilterBackend,] serializer_class = NewSerializers pagination_class = PageNumberPagination
14 .视图举例:
实现 表Tag的增删改查
from django.db import models class Tag(models.Model): title =models.CharField(max_length=32)
from django.conf.urls import url,include from django.contrib import admin from shanxuan import views urlpatterns = [ url(r'^tag/$', views.Tagviews.as_view()), url(r'^tag/(?P<pk>\d+)/$', views.TagDetailviews.as_view()), ]
查询单条数据和全部的 不能写在一起,会被覆盖掉,所以写两个views
from rest_framework import serializers from rest_framework.generics import ListAPIView,RetrieveAPIView,CreateAPIView,UpdateAPIView,DestroyAPIView from . import models class TagSer(serializers.ModelSerializer): class Meta: model = models.Tag fields = "__all__" class Tagviews(ListAPIView,CreateAPIView): queryset = models.Tag.objects.all() serializer_class = TagSer class TagDetailviews(RetrieveAPIView,UpdateAPIView,DestroyAPIView): queryset = models.Tag.objects.all() serializer_class = TagSer
分页写在配置文件中
REST_FRAMEWORK = { 'PAGE_SIZE':2, 'DEFAULT_PAGINATION_CLASS':"rest_framework.pagination.PageNumberPagination" }
15.自定制方法
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): # 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) class TagDetailView(RetrieveAPIView,UpdateAPIView,DestroyAPIView): queryset = models.Tag.objects.all() serializer_class = TagSer
16.类继承关系
class View(object): def dipatch(self): print(123) class APIView(View): version_class = settings.xxx parser_class = settings.sxx permision_classes = [] def dipatch(self): self.initial() method = getattr(self,"get") return method() def initial(self): self.version_class() self.parser_class() for item in self.permision_classes: item() class GenericAPIView(APIView): queryset = None serilizer_class = None def get_queryset(self): return self.queryset def get_serilizer(self,*arg,**kwargs): cls = self.get_serilizer_class() return cls(*arg,**kwargs) def get_serilizer_class(self): return self.serilizer_class class ListModelMixin(object): def list(self): queryset = self.get_queryset() ser = self.get_serilizer(queryset,many=True) return Reponse(ser.data) class ListAPIView(ListModelMixin,GenericAPIView): def get(self): return self.list(...) class TagView(ListAPIView): queryset = models.User.object.all() serilizer_class = TagSerilizer version_class = URLPathClass parser_class = JSONParser permission_classes = [Foo,Bar ] obj = TagView() x = obj.dispatch() 给用户返回x
17.版本
在url中显示版本信息,
http://127.0.0.1:8000/version/v1/index
urlpatterns +=[ url(r'^version/',include('version.urls')) ]
urlpatterns = [ url(r'^(?P<version>\w+)/index/$',views.IndexViews.as_view()), ]
class IndexViews(APIView): #versioning_class = URLPathVersioning #这是局部 def get(self,request,*args,**kwargs): print(request.version) print(request.versioning_scheme) return Response("123")
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', }
18.认证
流程
1.新建项目,创建数据库
class UserInfo(models.Model): username = models.CharField(max_length=32) password = models.CharField(max_length=32) token = models.CharField(max_length=64,null=True,blank=True)
2.url
from django.conf.urls import url,include from django.contrib import admin from . import views urlpatterns = [ url(r'^login/$', views.LoginView.as_view()), url(r'^order/$', views.OrderView.as_view()), ]
3.views
import uuid from django.shortcuts import render from django.views import View from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator from rest_framework.versioning import URLPathVersioning from rest_framework.views import APIView from rest_framework.response import Response from . import models #创建token随机字符创,添加到数据库 class LoginView(APIView): def post(self,request,*args,**kwargs): user_obj = models.UserInfo.objects.filter(**request.data).first() if not user_obj: return Response('登录失败') strs = str(uuid.uuid4()) user_obj.token = strs user_obj.save() return Response(strs) #判断条件,用户是否存在 from rest_framework.authentication import BaseAuthentication class TokenAuthentication(BaseAuthentication): def authenticate(self, request): token = request.query_params.get('token') user_obj = models.UserInfo.objects.filter(token=token).first() if user_obj: return (user_obj,token) return (None,None) #登录验证后缀有token返回正常,无返回gun class OrderView(APIView): authentication_classes = [TokenAuthentication, ] def get(self,request,*args,**kwargs): #print(request.user) #print(request.auth) if request.user: return Response("123") return Response('gun')
4. 简化代码
全局配置
需要把TokenAuthentication 单独提出放到auth.py中
setting中配置
'DEFAULT_AUTHENTICATION_CLASSES':['renzheng.auth.TokenAuthentication',]
5.postman测试
http://127.0.0.1:8000/renzheng/order/?token=df2d6aba-187b-4e9c-af1b-fe025fdffde3 http://127.0.0.1:8000/renzheng/order/
总结
当用户发来请求时, dispash 找到认证的所有类并实例化成为对象列表,然后将对象列表封装到新的request对象中。 以后在视同中调用request.user 在内部会循环认证的对象列表,并执行每个对象的authenticate方法,该方法用于认证,他会返回两个值分别会赋值给 request.user/request.auth
19.权限
其他同上
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import BasePermission #判断是否有权限 class MyPermission(BasePermission): message ={'error':"无权限"} #方式一 #多条数据 def has_permission(self, request, view): if request.user: return True return False # from rest_framework import exceptions # return exceptions.PermissionDenied({'error':"无权限"}) #单条数据 def has_object_permission(self, request, view, obj): return False class OrderView(APIView): permission_classes = [MyPermission,] def get(self,request,*args,**kwargs): return Response('123')
全局的话
REST_FRAMEWORK = { "DEFAULT_PERMISSION_CLASSES":["",] }
源码分析
class APIView(View): permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES def dispatch(self, request, *args, **kwargs): 封装request对象 self.initial(request, *args, **kwargs) 通过反射执行视图中的方法 def initial(self, request, *args, **kwargs): 版本的处理 # 认证 self.perform_authentication(request) # 权限判断 self.check_permissions(request) self.check_throttles(request) def perform_authentication(self, request): request.user 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')
20.跨域
由于浏览器具有“同源策略”的限制。 如果在同一个域下发送ajax请求,浏览器的同源策略不会阻止。 如果在不同域下发送ajax,浏览器的同源策略会阻止。
- 域相同,永远不会存在跨域。
- 非前后端分离,没有跨域。
- 前后端分离,nginx分流不存在跨域。
- 域不同时,才会存在跨域。
- l拉勾网,前后端分离,存在跨域(设置响应头解决跨域)
1.解决跨域:CORS
本质在数据返回值设置响应头 #在服务端 from django.shortcuts import render,HttpResponse def json(request): response = HttpResponse("JSONasdfasdf") response['Access-Control-Allow-Origin'] = "*" return response
2.跨域时,发送了2次请求
在跨域时,发送的请求会分为两种:
条件: 1、请求方式:HEAD、GET、POST 2、请求头信息: Accept Accept-Language Content-Language Last-Event-ID Content-Type 对应的值是以下三个中的任意一个 application/x-www-form-urlencoded multipart/form-data text/plain 注意:同时满足以上两个条件时,则是简单请求,否则为复杂请求
简单请求,发一次请求。
设置响应头就可以解决 from django.shortcuts import render,HttpResponse def json(request): response = HttpResponse("JSONasdfasdf") response['Access-Control-Allow-Origin'] = "*" return response
复杂请求,发两次请求。
- 预检
- 请求
from django.views.decorators.csrf import csrf_exempt @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
3.总结
- 由于浏览器具有“同源策略”的限制,所以在浏览器上跨域发送Ajax请求时,会被浏览器阻止。
- 解决跨域
- 不跨域
- CORS(跨站资源共享,本质是设置响应头来解决)。
- 简单请求:发送一次请求
- 复杂请求:发送两次请求
21.访问频率限制
用法
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.throttling import AnonRateThrottle,BaseThrottle#### class ArticleView(APIView): throttle_classes = [AnonRateThrottle,] ##### def get(self,request,*args,**kwargs): return Response('文章列表') class ArticleDetailView(APIView): def get(self,request,*args,**kwargs): return Response('文章详细')
REST_FRAMEWORK = { 'DEFAULT_VERSIONING_CLASS':"rest_framework.versioning.URLPathVersioning", "ALLOWED_VERSIONS":['v1',], "DEFAULT_THROTTLE_RATES":{ ############## "anon":'3/m' } } #全局DEFAULT_AUTHENTICATION_CLASSES=[rest_framework.throttling.AnonRateThrottle]
源码
class BaseThrottle: """ Rate throttling of requests. """ def allow_request(self, request, view): """ Return `True` if the request should be allowed, `False` otherwise. """ raise NotImplementedError('.allow_request() must be overridden') def get_ident(self, request): """ Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR if present and number of proxies is > 0. If not use all of HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR. """ xff = request.META.get('HTTP_X_FORWARDED_FOR') remote_addr = request.META.get('REMOTE_ADDR') num_proxies = api_settings.NUM_PROXIES if num_proxies is not None: if num_proxies == 0 or xff is None: return remote_addr addrs = xff.split(',') client_addr = addrs[-min(num_proxies, len(addrs))] return client_addr.strip() return ''.join(xff.split()) if xff else remote_addr def wait(self): """ Optionally, return a recommended number of seconds to wait before the next request. """ return None class SimpleRateThrottle(BaseThrottle): """ A simple cache implementation, that only requires `.get_cache_key()` to be overridden. The rate (requests / seconds) is set by a `rate` attribute on the View class. The attribute is a string of the form 'number_of_requests/period'. Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day') Previous request information used for throttling is stored in the cache. """ cache = default_cache timer = time.time cache_format = 'throttle_%(scope)s_%(ident)s' scope = None THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES def __init__(self): if not getattr(self, 'rate', None): self.rate = self.get_rate() self.num_requests, self.duration = self.parse_rate(self.rate) def get_cache_key(self, request, view): """ Should return a unique cache-key which can be used for throttling. Must be overridden. May return `None` if the request should not be throttled. """ raise NotImplementedError('.get_cache_key() must be overridden') def get_rate(self): """ Determine the string representation of the allowed request rate. """ if not getattr(self, 'scope', None): msg = ("You must set either `.scope` or `.rate` for '%s' throttle" % self.__class__.__name__) raise ImproperlyConfigured(msg) try: return self.THROTTLE_RATES[self.scope] except KeyError: msg = "No default throttle rate set for '%s' scope" % self.scope raise ImproperlyConfigured(msg) def parse_rate(self, rate): """ Given the request rate string, return a two tuple of: <allowed number of requests>, <period of time in seconds> """ if rate is None: return (None, None) num, period = rate.split('/') num_requests = int(num) duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]] return (num_requests, duration) def allow_request(self, request, view): """ Implement the check to see if the request should be throttled. On success calls `throttle_success`. On failure calls `throttle_failure`. """ 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: return self.throttle_failure() return self.throttle_success() def throttle_success(self): """ Inserts the current request's timestamp along with the key into the cache. """ self.history.insert(0, self.now) self.cache.set(self.key, self.history, self.duration) return True def throttle_failure(self): """ Called when a request to the API has failed due to throttling. """ return False def wait(self): """ Returns the recommended next request time in seconds. """ if self.history: remaining_duration = self.duration - (self.now - self.history[-1]) else: remaining_duration = self.duration available_requests = self.num_requests - len(self.history) + 1 if available_requests <= 0: return None return remaining_duration / float(available_requests) class AnonRateThrottle(SimpleRateThrottle): """ Limits the rate of API calls that may be made by a anonymous users. The IP address of the request will be used as the unique cache key. """ scope = 'anon' def get_cache_key(self, request, view): if request.user.is_authenticated: return None # Only throttle unauthenticated requests. return self.cache_format % { 'scope': self.scope, 'ident': self.get_ident(request) }
总结
如何实现的评率限制
- 匿名用户,用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.可以访问
22..jwt
用于在前后端分离时,实现用户登录相关。
一般用户认证有2中方式:
token
用户登录成功之后,生成一个随机字符串,自己保留一分+给前端返回一份。 以后前端再来发请求时,需要携带字符串。 后端对字符串进行校验。
优势:
- token只在前端保存,后端只负责校验。
- 内部集成了超时时间,后端可以根据时间进行校验是否超时。
- 由于内部存在hash256加密,所以用户不可以修改token,只要一修改就认证失败。
jwt
用户登录成功之后,生成一个随机字符串,给前端。 - 生成随机字符串 {typ:"jwt","alg":'HS256'} {id:1,username:'alx','exp':10} 98qow39df0lj980945lkdjflo.saueoja8979284sdfsdf.asiuokjd978928374 - 类型信息通过base64加密 - 数据通过base64加密 - 两个密文拼接在h256加密+加盐 base64url - 给前端返回 98qow39df0lj980945lkdjflo.saueoja8979284sdfsdf.asiuokjd978928375 前端获取随机字符串之后,保留起来。 以后再来发送请求时,携带98qow39df0lj980945lkdjflo.saueoja8979284sdfsdf.asiuokjd978928375。 后端接受到之后, 1.先做时间判断 2.字符串合法性校验。
安装
pip3 install djangorestframework-jwt
案例
app中注册
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' ]
用户登录
import uuid from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.versioning import URLPathVersioning from rest_framework import status from api import models class LoginView(APIView): """ 登录接口 """ def post(self,request,*args,**kwargs): #1.根据用户名和密码验证用户登录 user = models.UserInfo.objects.filter(username=request.data.get('username'),password=request.data.get('password')).first() if not user: return Response({'code':10001,'error':"用户名密码错误"}) #2.根据user对象生成payload(中间值的数据) jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER payload = jwt_payload_handler(user) #3.构造前面数据,base64加密,中间数据base64加密,前两段拼接然后做hs256加密(加盐),在做base64加密.生成token jwt_encode_hander = api_settings.JWT_ENCODE_HANDLER token = jwt_encode_hander(payload) return Response({'code':10000,'data':token})
用户认证
from rest_framework.views import APIView from rest_framework.response import Response # from rest_framework.throttling import AnonRateThrottle,BaseThrottle class ArticleView(APIView): # throttle_classes = [AnonRateThrottle,] def get(self,request,*args,**kwargs): # 获取用户提交的token,进行一步一步校验 import jwt from rest_framework import exceptions from rest_framework_jwt.settings import api_settings jwt_decode_handler = api_settings.JWT_DECODE_HANDLER jwt_value = request.query_params.get('token') try: payload = jwt_decode_handler(jwt_value) except jwt.ExpiredSignature: msg = '签名已过期' raise exceptions.AuthenticationFailed(msg) except jwt.DecodeError: msg = '认证失败' raise exceptions.AuthenticationFailed(msg) except jwt.InvalidTokenError: raise exceptions.AuthenticationFailed() print(payload) return Response('文章列表')