REST framework:
前提小知识:
1.Django CBV:
URL:url(r'^/index',view.StudentView.as_view())
view: class StudentView(view):
def get(self,request,*args,**kwargs):
pass
def post(self,request,*args,**kwargs):
pass
...
流程:request -> 路由 -> as_view() -> view() ->dispath() (基于反射进行路由分发)
2. [item() for item [Foo,Bar]] # 实例化两个类的实例,放入列表中
3. return JsonResponse(result, status=200) 使用JsonRespone 自动json.dumps(),from django.http import JsonResponse
4. csrf_token 中间件是在process_view中执行,1.加上装饰器@csrf_exempt 免除认证 2.装饰器@csrf_protect 添加csrf认证
5.中间件的过程:请求-> process_request1-3 ->路由 ->process_view1-3 -> 视图 -> process_exception3-1 -> process_response3-1 ->响应
还有一个process_template_XXX
6.请求头有哪些:
HOST,Cookie,eferer,Accept-Language,Accept-Encoding,User-Agent
7.响应头:
Allow允许的请求方式,Content-Encoding,Content-Length,Content-Type,Date,Expires,Refresh,Server,Set-Cookie,Location,Last-Modified
8.remote_addr = request.META.get('REMOTE_ADDR')获取请求IP
9.request.body 中的值,只有在Content-type:application/x-www-form-urlencoded 并且是post提交时data为name='mihon'&age=18这种数据类型,
自动将request.body值转换传送给request.POST
概述:restful代表的是一种软件架构风格,与技术无关,表征状态转移,将前后端分离,它将分布在网络中某个节点的资源通过
URL进行标识,客户端应用通过URL来获取资源的表征,获取这些表征致使这些应用状态改变。
http方法 资源操作 幂等 安全
GET SELSET Y Y
POSE INSERT N N
PUT UPDATE Y N
DELETE DELETE Y N
幂等性:对同一REST接口的多次访问,得到的资源状态是相同的
安全性:对改REST接口访问,不会使服务器端资源的状态发生改变
retful API 设计规范:
1.API与用户的通信协议,建议使用HTTPs(安全,需要证书但是需要钱)
2.域名
.https://api.example.com 将api部署在专用域名(会存在跨域问题)
.https://example.com/api 添加api到路由上
3.版本
.url https://example.com/api/v1/
.请求头 跨域时,引发发送多次请求
4.路径
.https://example.com/api/v1/zoos 均使用名词表示(可以复数)
5.请求方法
.GET 获取资源
.POST 新建资源
.PUT 更新资源,完整更新
.PATCH 更新资源,改变属性
.DELETE 删除资源
6.过滤
https://example.com/api/v1/zoos?limit=10&page=2
7.状态码
200 ok,请求成功
201 新建或修改数据成功
202 表示一个请求已经进入后台排队
204 用户数据删除成功
400 用户请求错误,没有进行数据的新建或者修改
401 表示用户没有权限
403 表示用户没有权限,访问被禁止forbidden csrf_token
404 表示用户请求的资源不存在
410 用户请求的资源被永久删除
422 当创建一个对象是,发生一个验证错误
500 服务器发生错误,
8.错误处理
返回错误时error当作key{error:"Invalid API key"}
9.返回结果,针对不同操作
/collection 返回资源对象的列表
/collection/resource 返回当个资源对象
10.返回结果中提供详细页面的链接
{
1:"apple"
detail:"https://order/apple/34"
}
基于Django实现:
class TestView(APIView):
def dispatch(self, request, *args, **kwargs):
"""
请求到来之后,都要执行dispatch方法,dispatch方法根据请求方式不同触发 get/post/put等方法
注意:APIView中的dispatch方法有好多好多的功能
"""
return super().dispatch(request, *args, **kwargs) #执行父类的dispath方法,将request进行封装
用户认证:
流程:需要创建一个CBV
1.url(r'^test/', TestView.as_view()),]
2.创建一个TestAuthentication(BaseAuthentication)的类并且继承BaseAuthentication
3.TestAuthentication 实现两个方法 authenticate,authenticate_header(用于认证错误时)
4.创建一个CBV类TestView(APIView)继承APIView
5.如果没有在setting中设置全局配置,需要在CBV中添加 authentication_classes = [TestAuthentication, ]
6.全局配置,在setting中REST_FRAMEWORK ={}添加两个键值对:
1.当认证通过没有返回元组值时,返回设置的值None(默认是匿名用户)
2.添加认证类的全路径"DEFAULT_AUTHENTICATION_CLASSES":["web.utils.TestAuthentication",]
url:urlpatterns = [
url(r'^test/', TestView.as_view()),]
view:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.authentication import BaseAuthentication
from rest_framework.request import Request
from rest_framework import exceptions
token_list = [
'sfsfss123kuf3j123',
'asijnfowerkkf9812',
]
class TestAuthentication(BaseAuthentication):
def authenticate(self, request):
"""
用户认证,如果验证成功后返回元组: (用户,用户Token) 返回后可以通过 request.user request.auth 调用
:param request:
:return:
None,表示跳过该验证;
如果跳过了所有认证,默认用户和Token和使用配置文件进行设置
self._authenticator = None
if api_settings.UNAUTHENTICATED_USER:
self.user = api_settings.UNAUTHENTICATED_USER()
else:
self.user = None
if api_settings.UNAUTHENTICATED_TOKEN:
self.auth = api_settings.UNAUTHENTICATED_TOKEN()
else:
self.auth = None
(user,token)表示验证通过并设置用户名和Token;
AuthenticationFailed异常
"""
val = request.query_params.get('token')
if val not in token_list:
raise exceptions.AuthenticationFailed("用户认证失败")
return ('登录用户', '用户token')
def authenticate_header(self, request):
"""
Return a string to be used as the value of the `WWW-Authenticate`
header in a `401 Unauthenticated` response, or `None` if the
authentication scheme should return `403 Permission Denied` responses.
"""
# 验证失败时,返回的响应头WWW-Authenticate对应的值
pass
class TestView(APIView):
authentication_classes = [TestAuthentication, ]
permission_classes = []
def get(self, request, *args, **kwargs):
print(request.user)
print(request.auth)
return Response('GET请求,响应内容')
def post(self, request, *args, **kwargs):
return Response('POST请求,响应内容')
def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容')
全局使用:在setting 中配置,并且认证函数和view函数分离,在view函数中就可以不需要写authentication_classes = [TestAuthentication, ]
但是,login视图是不需要走认证的因此authentication_classes = []写一个空列表就可以。
REST_FRAMEWORK = {
'UNAUTHENTICATED_USER': None,
'UNAUTHENTICATED_TOKEN': None,
"DEFAULT_AUTHENTICATION_CLASSES": [
"web.utils.TestAuthentication", # 需要写全路径,根目录为当前工程的顶级目录
],
"DEFAULT_PERMISSION_CLASSES": [
"web.utils.TestPermission",
],
}
权限设置:
1.写一个权限类 TestPermission(BasePermission)继承BasePermission
2.添加一个静态字段message="权限认证失败",当权限认证失败,has_permission()返回False时抛异常,值为message设置的值
3.实现一个两个方法:
1.has_permission()权限认证成功返回True 认证失败返回Flse
2.has_obj_permission()视图继承GenericAPIView,并在其中使用get_object获取对象,触发单独对象权限认证
4.在CBV中添加静态字段permission_classes = [TestPermission,]
5.全局配置"DEFAULT_PERMISSION_CLASSES": ["web.utils.TestPermission",]
节流/频率限制:
1.添加频率限制类 LuffyAnonRateThrottle(SimpleRateThrottle)继承SimpleThrottle
2.如果需要在setting中全局配置,在LuffyAnonRateThrottle类中添加静态字段 scope = "luffy_anon"
3.在频率限制类中实现两个方法和一个字段:
scope = 'luffy_anon' #在源码中rate会获取scope的值,没有就会抛出异常
1.def get_ident(self, request):
"""
认证成功时:request.user是用户对象;request.auth是token对象
:param request:
:return:
"""
# return request.auth.token
return "user_token" #使用user_token限制
2.def get_cache_key(self, request, view): 必须重写
# 用户已登录,则跳过 匿名频率限制
if request.user:
return None #放回None 表示不做限制
return self.cache_format % {
'scope': self.scope,
'ident': self.get_ident(request) #如果没有实现get_ident(self, request)方法就使用继承类的IP限制
} #需要返回 self.cache_format
4.如果没有在setting中设置全局设置,需要在view中throttle_classes = [LuffyUserRateThrottle, LuffyAnonRateThrottle, ]
5.自定制超过频率返回中文错误,在view中添加
def throttled(self, request, wait):
"""
访问次数被限制时,定制错误信息
"""
class Throttled(exceptions.Throttled):
default_detail = '请求被限制.'
extra_detail_singular = '请 {wait} 秒之后再重试.'
extra_detail_plural = '请 {wait} 秒之后再重试.'
raise Throttled(wait)
6.全局setting中设置:
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'api.utils.throttles.throttles.LuffyAnonRateThrottle', #类的路径
'api.utils.throttles.throttles.LuffyUserRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '10/day',
'user': '10/day',
'luffy_anon': '10/m',
'luffy_user': '20/m', #频率设置
},}
版本:
1.基于url eg:/users?version=v1
.setting设置
REST_FRAMEWORK = {
'DEFAULT_VERSION': 'v1', # 默认版本
'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本
'VERSION_PARAM': 'version' # URL中获取值的key
}
.view中添加字段versioning_class = QueryParameterVersioning #不能是列表,QueryParameterVersioning 是URL的类
2.基于URL正则 eg:/v1/users/
.setting设置
REST_FRAMEWORK = {
'DEFAULT_VERSION': 'v1', # 默认版本
'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本
'VERSION_PARAM': 'version' # URL中获取值的key
}
.url路由正则:
url(r'^(?P<version>[v1|v2]+)/test/', TestView.as_view(), name='test'),
.view中添加字段 versioning_class = URLPathVersioning # URLPathVersioning是URL正则的类
3.基于accept请求头 eg: Accept: application/json; version=1.0
.setting设置
REST_FRAMEWORK = {
'DEFAULT_VERSION': 'v1', # 默认版本
'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本
'VERSION_PARAM': 'version' # URL中获取值的key
}
.view中添加字段 versioning_class = AcceptHeaderVersioning # AcceptHeaderVersioning请求头的类
4.基于主机 eg: v1.example.com
.setting设置同上
.view中添加字段 versioning_class = HostNameVersioning # HostNameVersioning 从主机上获取version的类
5.基于Django的namespace
.versioning_class = NamespaceVersioning
6.全局设置:
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS':"rest_framework.versioning.URLPathVersioning", #路径
'DEFAULT_VERSION': 'v1',
'ALLOWED_VERSIONS': ['v1', 'v2'],
'VERSION_PARAM': 'version' }
7.使用
class TestView(APIView):
versioning_class = NamespaceVersioning
def get(self, request, *args, **kwargs):
# 获取版本
print(request.version)
# 获取版本管理的类
print(request.versioning_scheme)
# 反向生成URL
reverse_url = request.versioning_scheme.reverse('test', request=request)
print(reverse_url)
return Response('GET请求,响应内容')
def post(self, request, *args, **kwargs):
return Response('POST请求,响应内容')
def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容')
解析器(parser):
.同时使用多个parser,rest framework 会根据请求头content-type自动比对,并使用对应parser
parser_classes = [JSONParser, FormParser, MultiPartParser, ]
.全局使用
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES':[
'rest_framework.parsers.JSONParser' #请求头content-type:application/json
'rest_framework.parsers.FormParser' #请求头content-type:application/x-www-form-urlencoded
'rest_framework.parsers.MultiPartParser' #请求头content-type:multipart/form-data
]}
.使用:request.data 转换的数据被封装到request中
序列化:
1. ll = serializers.HyperlinkedIdentityField(view_name='xxxx') detail使用地址链接, context={'request': request}
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from .. import models
class PasswordValidator(object):
def __init__(self, base):
self.base = str(base)
def __call__(self, value):
if value != self.base:
message = 'This field must be %s.' % self.base
raise serializers.ValidationError(message)
def set_context(self, serializer_field):
"""
This hook is called by the serializer instance,
prior to the validation call being made.
"""
# 执行验证之前调用,serializer_fields是当前字段对象
pass
class ModelUserSerializer(serializers.HyperlinkedModelSerializer):
ll = serializers.HyperlinkedIdentityField(view_name='xxxx')
tt = serializers.CharField(required=False)
class Meta:
model = models.UserInfo
fields = "__all__"
list_serializer_class = serializers.ListSerializer
# fields = ['user', 'pwd', 'ut']
#depth = 2
extra_kwargs = {
'user': {'min_length': 6},
'pwd': {'validators': [PasswordValidator(666), ]},
'url': {'view_name': 'xxxx'},
'ut': {'view_name': 'xxxx'},
}
class TestView(APIView):
def get(self, request, *args, **kwargs):
# # 序列化,将数据库查询字段序列化为字典
data_list = models.UserInfo.objects.all()
ser = ModelUserSerializer(instance=data_list, many=True, context={'request': request})
# # 如果Many=True
# # 或
# # obj = models.UserInfo.objects.all().first()
# # ser = UserSerializer(instance=obj, many=False)
return Response(ser.data)
def post(self, request, *args, **kwargs):
# 验证,对请求发来的数据进行验证
print(request.data)
ser = ModelUserSerializer(data=request.data)
if ser.is_valid():
print(ser.validated_data)
else:
print(ser.errors)
return Response('POST请求,响应内容')
分页:
1.根据数据和页码分页
class StandardResultsSetPagination(PageNumberPagination):
# 默认每页显示的数据条数
page_size = 1
# 获取URL参数中设置的每页显示数据条数
page_size_query_param = 'page_size'
# 获取URL参数中传入的页码key
page_query_param = 'page'
# 最大支持的每页显示的数据条数
max_page_size = 1
2.根据位置和个数进行分页
class StandardResultsSetPagination(LimitOffsetPagination):
# 默认每页显示的数据条数
default_limit = 10
# URL中传入的显示数据条数的参数
limit_query_param = 'limit'
# URL中传入的数据位置的参数
offset_query_param = 'offset'
# 最大每页显得条数
max_limit = None
3.根据游标进行分页
class StandardResultsSetPagination(CursorPagination):
# URL传入的游标参数
cursor_query_param = 'cursor'
# 默认每页显示的数据条数
page_size = 2
# URL传入的每页显示条数的参数
page_size_query_param = 'page_size'
# 每页显示数据最大条数
max_page_size = 1000
# 根据ID从大到小排列
ordering = "id"
class UserViewSet(APIView):
def get(self, request, *args, **kwargs):
user_list = models.UserInfo.objects.all().order_by('-id')
# 实例化分页对象,获取数据库中的分页数据
paginator = StandardResultsSetPagination()
page_user_list = paginator.paginate_queryset(user_list, self.request, view=self)
# 序列化对象
serializer = UserSerializer(page_user_list, many=True)
# 生成分页和数据
response = paginator.get_paginated_response(serializer.data)
return response
渲染器:
如果同时多个存在时,自动根据URL后缀来选择渲染器。
1.Json渲染器
http://127.0.0.1:8000/test/?format=json
http://127.0.0.1:8000/test.json
renderer_classes = [JSONRenderer, ]
2.Adnin表格渲染器
http://127.0.0.1:8000/test/?format=admin
http://127.0.0.1:8000/test.admin
renderer_classes = [AdminRenderer, ]
3.From表单渲染器
http://127.0.0.1:8000/test/?format=form
http://127.0.0.1:8000/test.form
renderer_classes = [HTMLFormRenderer, ]
4.自定义模板
http://127.0.0.1:8000/test/?format=html
http://127.0.0.1:8000/test.html
APIView:return Response(ser.data, template_name='user_detail.html')
5.浏览器格式API+JSONParser
http://127.0.0.1:8000/test/?format=api
http://127.0.0.1:8000/test.api
路由系统:
半自动:
url(r'^test/(?P<pk>\d+)/$', s10_generic.UserViewSet.as_view(
{'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'})),
全自动:
router = routers.DefaultRouter()
router.register(r'users', s10_generic.UserViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
]