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('文章列表')