drf--Django rest_ framework
目录:
RESTful规范:面向资源编程
-与后台做交互,通常使用https
-包含认证书,更加具备安全性
-域名:
-https://api.baidu.com(存在跨域问题)
-https://www.baidu.com/api/
-版本
-https://www.baidu.com/api/v1
-https://www.baidu.com/api/v2
-路径,视网络上任何东西都是资源,均使用名词表示(可复数)
-https://api.example.com/v1/books
不是:https://api.example.com/v1/delete_one_book
-method来表示增删查改
-https://api.example.com/v1/books get请求,获取所有书 -https://api.example.com/v1/books post请求,新增一本书 -https://api.example.com/v1/book/1 delete请求,删除一本书 -https://api.example.com/v1/book/1 delete请求,获取id为1的这本书 -https://api.example.com/v1/book/1 put/patch请求,修改id为1的这本书
-过滤,通过在url上传参的形式传递搜索条件
-https://api.example.com/v1/books?limit=10:只拿前10本
-https://api.example.com/v1/books?price=10:只拿价格为10的书
-状态码
-{status:100,}
-错误处理,应返回错误信息,error当做key
{ status:101, errors:'您没有权限操作这个事' }
-返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范
GET /collection:返回资源对象的列表(数组) GET /collection/resource:返回单个资源对象 POST /collection:返回新生成的资源对象 PUT /collection/resource:返回完整的资源对象 PATCH /collection/resource:返回完整的资源对象 DELETE /collection/resource:返回一个空文档
-返回结果中提供链接(获取一本书)
{ id:1 name:lxx price:12 publish:www.xx.com/api/v1/publish/1 }
APIView
如果要在浏览器访问需要在app中注册:rest_framework
重写dispatch方法: # 传入的request是原生的request对象 # 这个request已经不是原生的request了,但是它内部有个原生的request对象 request = self.initialize_request(request, *args, **kwargs) self.initial(request, *args, **kwargs)#这里面有权限,认证,频率
序列化组件
--
Serializer:
-1 写一个类继承 class BookSerializer(serializers.Serializer): id = serializers.CharField() title = serializers.CharField() price = serializers.CharField() -2 使用(视图类中) book_ser=BookSerializer(queryset对象/单个对象,many=True/False) book_ser.data 序列化完成的字典
ModelSerializer:
class BookSerializer(serializers.ModelSerializer):
class Meta:
# 指定表模型
model = models.Book
# 序列化所有字段
fields = '__all__'
# 只想序列化title和id这俩字段
fields = ['title', 'id']
# exclude 和fields 不要连用
exclude = ['title']
#将查询自动进行到下一层,不推荐使用,不能自定义显示
depth=1
序列化组件的保存和修改功能:
序列化类必须继承自modelserializer类,反序列化数据,验证,保存
def post(self, request): response = {'status': 100, 'msg': '成功'} # 提交的字典 # book = request.data # 传统方法,创建对象保存 # 新方法,通过序列化组件保存,必须用继承自ModelSerializer # data注意是data book_ser = BookSerializer(data=request.data) # is_valid提交的字段校验通过 if book_ser.is_valid(): book_ser.save() response['book'] = book_ser.data else: response['msg'] = book_ser.errors # return Response(book_ser.data) return Response(response) def put(self, request, id): response = {'status': 100, 'msg': '成功'} book = models.Book.objects.filter(pk=id).first() # 修改的话,要把book对象传过来,增加和修改的区别 book_ser = BookSerializer(data=request.data, instance=book) # is_valid提交的字段校验通过 if book_ser.is_valid(): # save既可以修改,又可以更新 book_ser.save() response['book'] = book_ser.data else: response['msg'] = book_ser.errors # return Response(book_ser.data) return Response(response)
局部和全局钩子函数:
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('sb'):
raise exceptions.ValidationError('不能以sb开头')
return value
总结:
1 变量名和source指定的值不能一样 2 source='publish.name'还支持继续 . 3 source 还支持方法(没用) 4 支持写方法,如下 #方法一定传一个参数,是当前book对象 publish_dic=serializers.SerializerMethodField() def get_publish_dic(self,obj): # obj应该是,当前book对象 return {'id':obj.publish.pk,'name':obj.publish.name}
source 如果是字段,会显示字段,如果是方法,会执行方法,不用加括号(authors=serializers.CharField(source='authors.all'))
如在模型中定义一个方法,直接可以在在source指定执行。
认证组件
--
认证功能:
1 写一个类,继承BaseAuthentication 2 def authenticate(self,request) ,记住传request对象 -如果验证通过,返回None或者两个值 3 在视图类中使用:(不要加括号) -authentication_classes=[AuthLogin]
认证的配置(在要认证操作前添加或禁用):
-认证功能的局部配置 -authentication_classes=[AuthLogin]
-认证功能的全局配置,在settings.py中配置 -REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.auth.AuthLogin",] } -全局使用的局部禁用: authentication_classes = [] -drf内置了一些认证类(了解): -TokenAuthentication -SessionAuthentication -BaseAuthentication(规范了接口,模拟其它语言接口的概念) -def authenticate(self, request): raise NotImplementedError(".authenticate() must be overridden.") 如果写了一个类,继承BaseAuthentication,但是没有重写authenticate,就会抛异常
代码(登录认证):
def get_token(): return uuid.uuid4() class Login(APIView): authentication_classes = [] # def get(self,request): # return Response(111) def post(self,request): response = {'status': 100, 'msg': None} name = request.data.get('name') print(name) pwd = request.data.get('pwd') # print(pwd) user = models.User.objects.filter(name=name,password=pwd).first() # print(user) if user: response['msg'] = '登录成功' token = get_token() models.User_token.objects.update_or_create(defaults={"token":token},user=user) response['token'] = token else: response['status'] = 101, response['msg'] = '登录失败' return Response(response) class Get_User(APIView): authentication_classes = [AuthLogin] def get(self,request): token = request.GET.get('token') print(token) return Response(123)
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/', views.Login.as_view()), url(r'^getuser/', views.Get_User.as_view()),
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import NotAuthenticated from app01 import models class AuthLogin(BaseAuthentication): def authenticate(self,request): token=request.GET.get('token') print(token) ret=models.User_token.objects.filter(token=token).first() print(ret) if ret: #说明这个人登录了 return ret.user,token # return None else: # 说明没有登录 raise NotAuthenticated('您没有登录') # 要写这个方法, def authenticate_header(self,request): pass
settings
权限组件
--
功能实现:
-写一个类:
class MyPer(BasePermission): message='您没有权限' def has_permission(self, request, view): # 取出当前登录用户 user = request.user # 取出当前登录用户类型的中文 tt = user.get_user_type_display() if user.user_type == 0: return True else: return False
使用:
-局部使用
在视图函数中
permission_classes=[MyPer]
-全局使用
在setting中 "DEFAULT_PERMISSION_CLASSES":['app01.auth.MyPer'],
频率组件
限制单位时间访问次数:
-限制每个ip地址一分钟访问10次
-写一个类
继承drf来实现:
from rest_framework.throttling import SimpleRateThrottle class VisitThrottle(SimpleRateThrottle): scope = 'xx' #一定要写它 # 必须重写这个方法,返回什么,就以什么作为访问频率限制的东西, def get_cache_key(self, request, view): # return self.get_ident(request) return request.META.get('REMOTE_ADDR') -在setting中配置: 'DEFAULT_THROTTLE_RATES': { #这个key跟scope对应,value值3/m 3/h 4/d 'xx': '3/m' }
自定义实现:
1 class MyTho(): 2 VISIT_RECORD = {} 3 def __init__(self): 4 self.history = None 5 def allow_request(self,request,view): 6 # 访问频率的逻辑 7 # {'ip地址':[16:13:39,16:13:19,],'ip地址2':[时间1,时间2],} 8 # (1)取出访问者ip 9 # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走 10 # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间, 11 # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过 12 # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败 13 #(1)取出访问者ip 14 # print(request.META) 15 # REMOTE_ADDR 就是访问者的ip:127.0.0.1 16 ip=request.META.get('REMOTE_ADDR') 17 import time 18 # 获取当前时间 19 ctime=time.time() 20 # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问 21 if ip not in self.VISIT_RECORD: 22 self.VISIT_RECORD[ip]=[ctime,] 23 # {'127.0.0.1':[时间1,时间1,]} 24 return True 25 # self.history=[时间1,时间1,] 26 self.history=self.VISIT_RECORD.get(ip) 27 # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间, 28 while self.history and ctime-self.history[-1]>60: 29 self.history.pop() 30 # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过 31 # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败 32 if len(self.history)<3: 33 self.history.insert(0,ctime) 34 return True 35 else: 36 return False 37 38 def wait(self): 39 import time 40 ctime = time.time() 41 # 还要等多久才能访问 42 return 60 - (ctime - self.history[-1])
使用:
-局部使用: throttle_classes = [VisitThrottle] -全局使用: "DEFAULT_THROTTLE_CLASSES":["app01.auth.VisitThrottle"], -问题?如果网站所有的接口都是一分钟访问10次,但是单独有一个接口要一分钟访问2次,如何做?
单独为其写一个类,来进行频率控制
分页组件:
--
简单分页:
page_size = api_settings.PAGE_SIZE :每页显示条数 page_query_param = 'page' :查询的页码数 page_size_query_param = None :每页最大显示条数 -如果我要的数据是第一页显示10条:http://127.0.0.1:8000/books/?ccc=10&page=1 -如果我要的是第二页显示5条:http://127.0.0.1:8000/books/?ccc=5&page=2 max_page_size = 8 :控制最大显示多少条 -如果我想第一页显示10000条,这种不合理,通过这个参数控制,最大显示8条
1 from rest_framework.pagination import PageNumberPagination 2 class Books(APIView): 3 def get(self, request): 4 response = {'status': 100, 'msg': '成功'} 5 # 生成一个PageNumberPagination的对象 6 page_sipmle=PageNumberPagination() 7 page_sipmle.page_size=4 8 # page_sipmle.page_query_param='aaa' 9 page_sipmle.page_size_query_param='ccc' 10 page_sipmle.max_page_size=8 11 book_list = models.Book.objects.all() 12 page_list = page_sipmle.paginate_queryset(book_list,request,self) 13 book_ser = BookSerializer(instance=page_list, many=True) 14 15 print(book_ser.data) 16 response['books'] = book_ser.data 17 return Response(response) 18 19 def post(self, request): 20 response = {'status': 100, 'msg': '成功'} 21 book_ser = BookSerializer(data=request.data) 22 if book_ser.is_valid(): 23 book_ser.save() 24 response['book'] = book_ser.data 25 else: 26 response['msg'] = book_ser.errors 27 return Response(response)
使用:
page_sipmle=PageNumberPagination() page_sipmle.page_size=4 # page_sipmle.page_query_param='aaa' page_sipmle.page_size_query_param='ccc' page_sipmle.max_page_size=8 book_list = models.Book.objects.all() page_list = page_sipmle.paginate_queryset(book_list,request,self) book_ser = BookSerializer(instance=page_list, many=True)
偏移分页:
default_limit = api_settings.PAGE_SIZE :默认偏移的条数 5 limit_query_param = 'limit' :偏移的条数 offset_query_param = 'offset' :是标杆 max_limit = None :最大偏移的条数
1 from rest_framework.pagination import LimitOffsetPagination 2 class Books(APIView): 3 def get(self, request): 4 response = {'status': 100, 'msg': '成功'} 5 # 生成一个PageNumberPagination的对象 6 page_limit=LimitOffsetPagination() 7 page_limit.default_limit=6 8 page_limit.max_limit=5 9 book_list = models.Book.objects.all() 10 page_list = page_limit.paginate_queryset(book_list,request,self) 11 book_ser = BookSerializer(instance=page_list, many=True) 12 print(book_ser.data) 13 response['books'] = book_ser.data 14 return Response(response)
举例:
default_limit = api_settings.PAGE_SIZE :默认偏移的条数 5 limit_query_param = 'limit' :偏移的条数 offset_query_param = 'offset' :是标杆 max_limit = None :最大偏移的条数
加密分页(游标分页):
cursor_query_param = 'cursor' page_size = api_settings.PAGE_SIZE :默认每页显示的条数 ordering = 'id' :按谁排序 4 5 6 10
1 from rest_framework.pagination import CursorPagination 2 class Books(APIView): 3 def get(self, request): 4 response = {'status': 100, 'msg': '成功'} 5 # 生成一个PageNumberPagination的对象 6 page_cursor=CursorPagination() 7 page_cursor.page_size=5 8 page_cursor.ordering="id" 9 book_list = models.Book.objects.all() 10 page_list = page_cursor.paginate_queryset(book_list,request,self) 11 book_ser = BookSerializer(instance=page_list, many=True) 12 print(book_ser.data) 13 response['books'] = book_ser.data 14 # return Response(response) 15 return page_cursor.get_paginated_response(book_ser.data)
视图组件
--
视图封装
--
视图组件使用
ViewSetMixin
ViewSetMixin 写在前面,先找ViewSetMixin的as_view方法
用了ViewSetMixin ,视图类中,不需要再写get,post,delete....这些函数了,函数名可以自定义
而且这个视图类,可以响应多条路由
使用:
-urls.py中 url(r'^publishs/', views.PublishView.as_view({'get': 'aaa','post':'ddd'})), url(r'^bbb/', views.PublishView.as_view({'get': 'bbb','post':'ccc'})), -视图类中: class PublishView(ViewSetMixin,APIView): def aaa(self,request): return Response({'status':100}) def bbb(self,request): return Response({'bb': "bbb"})
解析组件
根据请求头 content-type 选择对应的解析器对请求体内容进行处理。
能够解析前端传过来的什么格式数据(urlencoded,form-data,json),一般只用json来保障安全性
有application/json,x-www-form-urlencoded,form-data等格式
-源码从request.data进 -全局配置 -'DEFAULT_PARSER_CLASSES':['rest_framework.parsers.JSONParser'], -局部配置 parser_classes=[JSONParser,]
响应器(一般用默认,不用写)
根据 用户请求URL 或 用户可接受的类型,筛选出合适的 渲染组件。
可以根据请求客户端类型,返回不同格式数据。
用户请求URL:
http://127.0.0.1:8000/test/?format=json
http://127.0.0.1:8000/test.json
-局部配置 renderer_classes=[JSONRenderer,] -全局配置 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', )
url控制器
-传统的url配置 url(r'^books/$', views.BookView.as_view()), url(r'^books/(?P<pk>\d+)$', views.BookDetailView.as_view()), -半自动
--view要ViewSetMixin,重写as_view函数 url(r'^publish/$', views.PublishView.as_view({'get':'list','post':'create'})), url(r'^publish/(?P<pk>\d+)/$', views.PublishView.as_view({'get':'retrieve','put':'update','delete':'destroy'})), -全自动(了解) -能自动生成多条路由
版本控制
-在setting中配置 'DEFAULT_VERSION': 'v1', # 默认版本(从request对象里取不到,显示的默认值) 'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本 'VERSION_PARAM': 'version' # URL中获取值的key -局部使用 versioning_class = URLPathVersioning -全局使用 'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning',
url.py文件,可以用路由分发来使用不同版本
url(r'^(?P<version>[v1|v2]+)/publishs/', views.PublishView.as_view()), url(r'^(?P<version>[v1|v2]+)/publishs/', views.publish)

浙公网安备 33010602011771号