Django Rest Framework框架组件的执行流程
rest framework组件为我们提供了下面的这些功能: 按照HTTP请求的生命周期去记; 先是进入路由,在视图,进入dispatch()里面,然 后提供的是版本,权限,认证,频率,然后从 解析器()里面取数据,再序列化,分页,渲染器。
1.认证和授权
a. 用户url传入的token认证(作为url的参数传入)
from django.conf.urls import url, include
from web.viewsimport TestView
urlpatterns = [
url(r'^test/', TestView.as_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
from .models import *
class TokenAuth(BaseAuthentication):
def authenticate(self, request):
token = request.GET.get("token")
# request.query_params ---> request.GET
token_obj = Token.objects.filter(token=token).first()
if not token_obj:
raise exceptions.AuthenticationFailed("验证失败123!")
else:
return token_obj.user, token_obj.token
# 返回一个元组 request.user, request.auth
# def authenticate_header(self, request):
# pass
# 只是我们继承了BaseAuthentication的认证组件 就不用写了。
class TestView(APIView):
authentication_classes = [TokenAuth, ]
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请求,响应内容')
views.py
认证和权限
from django.conf.urls import url, include
from web.views import TestView
urlpatterns = [
url(r'^test/', TestView.as_view()),
]
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.authentication import BaseAuthentication
from rest_framework.permissions import BasePermission
from rest_framework.request import Request
from rest_framework import exceptions
from .models import *
class TokenAuth(BaseAuthentication):
def authenticate(self, request):
token = request.GET.get("token")
# request.query_params ---> request.GET
token_obj = Token.objects.filter(token=token).first()
if not token_obj:
raise exceptions.AuthenticationFailed("验证失败123!")
else:
return token_obj.user.pk, token_obj.token
# 返回一个元组 request.user, request.auth
# def authenticate_header(self, request):
# pass
# 只是我们继承了BaseAuthentication的认证组件 就不用写了。
class SVIPPermisson(BasePermission):
message = "没有SVIP权限" # 错误信息
def has_permission(self, request, view):
if request.user == 1: # request.user.pk 是从认证组件里面取的
return True
else:
return False #没有权限
class TestView(APIView):
# 认证的动作是由request.user触发
authentication_classes = [TestAuthentication, ]
# 权限
# 循环执行所有的权限
permission_classes = [TestPermission, ]
def get(self, request, *args, **kwargs):
# self.dispatch
print(request.user)
print(request.auth)
return Response('GET请求,响应内容')
def post(self, request, *args, **kwargs):
return Response('POST请求,响应内容')
views.py
全局使用权限和认证,就需要在settings里面去配置全局信息。
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"web.utils.TestAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"web.utils.TestPermission",
],
}
settings.py
2.用户访问次数/频率限制
a. 基于用户IP限制访问频率
这种方法是无法控制的,因为用户可以换代理IP,所以没有完全的限制。
from django.conf.urls import url, include
from web.views import TestView
urlpatterns = [
url(r'^test/', TestView.as_view()),
]
urls.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import time
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import exceptions
from rest_framework.throttling import BaseThrottle
from rest_framework.settings import api_settings
# 保存访问记录
RECORD = {
'用户IP': [12312139, 12312135, 12312133, ]
}
class TestThrottle(BaseThrottle):
ctime = time.time
def get_ident(self, request):
"""
根据用户IP和代理IP,当做请求者的唯一IP
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 allow_request(self, request, view):
"""
是否仍然在允许范围内
Return `True` if the request should be allowed, `False` otherwise.
:param request:
:param view:
:return: True,表示可以通过;False表示已超过限制,不允许访问
"""
# 获取用户唯一标识(如:IP)
# 允许一分钟访问10次
num_request = 10
time_request = 60
now = self.ctime()
ident = self.get_ident(request)
self.ident = ident
if ident not in RECORD:
RECORD[ident] = [now, ]
return True
history = RECORD[ident]
while history and history[-1] <= now - time_request: #剔除 超时或者是不符合的时间戳。
history.pop()
if len(history) < num_request: #判断当前IP的访问的次数(通过时间戳的数量)
history.insert(0, now) #把当前的访问的时间戳放在第一个。
return True
def wait(self):
"""
多少秒后可以允许继续访问
Optionally, return a recommended number of seconds to wait before
the next request.
"""
#last_time = RECORD[self.ident][0]
#last_time 就是代表最长时间后才可以继续访问。
min_time = RECORD[self.ident][-1]
#列表中最后一个是最小的时间,当最小的时间失效后就可以访问了。
now = self.ctime()
return int(60 + min_time - now) #还有多少秒可以继续访问,就代表着列表中最后一个时间失效剔除后,列表中的个数就少于次数了,这样不就可以访问了吗?也是可以访问的最小时间。
class TestView(APIView):
throttle_classes = [TestThrottle, ]
def get(self, request, *args, **kwargs):
# self.dispatch
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请求,响应内容')
def throttled(self, request, wait):
"""
访问次数被限制时,定制错误信息
"""
class Throttled(exceptions.Throttled):
default_detail = '请求被限制.'
extra_detail_singular = '请 {wait} 秒之后再重试.'
extra_detail_plural = '请 {wait} 秒之后再重试.'
raise Throttled(wait)
views.py
b. 基于用户IP显示访问频率(利于Django缓存)常用的
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'test_scope': '10/m',
},
}
在源码中
num, period = rate.split('/')
num_requests = int(num)
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
return (num_requests, duration)
即通过/分割,前面是允许访问的次数,后面是时间。
即在60s内可以访问的次数为10次
from django.conf.urls import url, include
from web.views import TestView
urlpatterns = [
url(r'^test/', TestView.as_view()),
]
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import exceptions
from rest_framework.throttling import SimpleRateThrottle
class TestThrottle(SimpleRateThrottle):
# 配置文件定义的显示频率的Key
scope = "test_scope"
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.
"""
if not request.user:
ident = self.get_ident(request)
else:
ident = request.user
return self.cache_format % {
'scope': self.scope,
'ident': ident
}
class TestView(APIView):
throttle_classes = [TestThrottle, ]
def get(self, request, *args, **kwargs):
# self.dispatch
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请求,响应内容')
def throttled(self, request, wait):
"""
访问次数被限制时,定制错误信息
"""
class Throttled(exceptions.Throttled):
default_detail = '请求被限制.'
extra_detail_singular = '请 {wait} 秒之后再重试.'
extra_detail_plural = '请 {wait} 秒之后再重试.'
raise Throttled(wait)
views.py
如何实现的访问频率控制?
访问频率控制的原理:
用户进来时候,把它的访问时间记录全部放进来,然后每次再访问进来时候,把超时或者是不符合的时间戳给剔除。
然后根据它时间记录的个数来做判断。
匿名用户:无法控制,因为用户可以换代理IP,所以没发做。
{
192.168.1.1:[1521223123.232, 1521223122.232, 1521223121.232],
192.168.1.2:[1521223123.232, 1521223122.232, 1521223121.232],
192.168.1.3:[1521223123.232, 1521223122.232, 1521223121.232],
192.168.1.4:[1521223123.232, 1521223122.232, 1521223121.232],
192.168.1.5:[1521223123.232, 1521223122.232, 1521223121.232],
192.168.1.6:[1521223123.232, 1521223122.232, 1521223121.232],
先剔除时间
}
真要做匿名用户的访问频率的话,可以给匿名用户给随机的字符串,然后给每次访问的时候都带着,用它来记录时间,但是这样也不行,因为浏览器如果不带的话,每次都是第一次访问。凭我现在的能力,还没有办法能解决,想问您有什么解决的好办法呢
登录用户:如果有很多账号,也无法限制
{
alex:[1521223123.232, 1521223122.232, 1521223121.232],
eric:[1521223123.232, 1521223122.232, 1521223121.232],
}
参考源码:from rest_framework.throttling import SimpleRateThrottle
SimpleRateThrottle请求频率源码分析
https://blog.csdn.net/u013210620/article/details/79898512
3.版本
a. 基于url的get传参方式(QueryParameterVersioning)
如:/users?version=v1
REST_FRAMEWORK = {
'DEFAULT_VERSION': 'v1', # 默认版本
'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本
'VERSION_PARAM': 'version' # URL中获取值的key
}
from django.conf.urls import url, include
from web.views import TestView
urlpatterns = [
url(r'^test/', TestView.as_view(),name='test'),
]
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import QueryParameterVersioning
class TestView(APIView):
versioning_class = QueryParameterVersioning
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请求,响应内容')
from rest_framework.versioning import URLPathVersioning,QueryParameterVersioning # URLPathVersioning 是把版本放在url上;最常用的 # QueryParameterVersioning 是把版本当做get的参数 ?version
基于url的正则方式(URLPathVersioning)
如:/v1/users/
REST_FRAMEWORK = {
'DEFAULT_VERSION': 'v1', # 默认版本
'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本
'VERSION_PARAM': 'version' # URL中获取值的key
}
from django.conf.urls import url, include
from web.views import TestView
urlpatterns = [
url(r'^(?P<version>[v1|v2]+)/test/', TestView.as_view(), name='test'),
]
rom rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import URLPathVersioning
class TestView(APIView):
versioning_class = URLPathVersioning
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请求,响应内容')
全局使用
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS':"rest_framework.versioning.URLPathVersioning",
'DEFAULT_VERSION': 'v1',
'ALLOWED_VERSIONS': ['v1', 'v2'],
'VERSION_PARAM': 'version'
}
更多的方式参考https://www.cnblogs.com/wupeiqi/articles/7805382.html
4. 解析器(parser)
#解析器 from rest_framework.parsers import JSONParser from rest_framework.parsers import FormParser from rest_framework.parsers import MultiPartParser from rest_framework.parsers import FileUploadParser # parser_classes = [JSONParser,FormParser,MultiPartParser] #解析器,即按照content-type的不同来处理数据 #仅处理请求头content-type为application/json的请求体为JSONParser #仅处理请求头content-type为application/x-www-form-urlencoded 的请求体为FormParser #仅处理请求头content-type为multipart/form-data的请求体为MultiPartParser #form表单上传文件
同时多个Parser,单个使用的使用就写需要用的那个
from django.conf.urls import url, include
from web.views import TestView
urlpatterns = [
url(r'test/', TestView.as_view(), name='test'),
]
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework.parsers import JSONParser, FormParser, MultiPartParser
class TestView(APIView):
parser_classes = [JSONParser, FormParser, MultiPartParser, ]
def post(self, request, *args, **kwargs):
print(request.content_type)
# 获取请求的值,并使用对应的JSONParser进行处理
print(request.data)
# application/x-www-form-urlencoded 或 multipart/form-data时,request.POST中才有值
print(request.POST)
print(request.FILES)
return Response('POST请求,响应内容')
def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容')
全局使用
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES':[
'rest_framework.parsers.JSONParser'
'rest_framework.parsers.FormParser'
'rest_framework.parsers.MultiPartParser'
]
}
注意:个别特殊的值可以通过Django的request对象 request._request 来进行获取
5. 序列化
序列化用于对用户请求数据进行验证和数据进行序列化
基于Model自动生成字段
from rest_framework import serializers
from app01.models import *
class CouserSerializers(serializers.ModelSerializer):
'''
课程的序列化
'''
#自定义的字段
#source为表里面的所有字段
level = serializers.CharField(source='get_level_display') #自动的帮你添加()
course_type = serializers.CharField(source='get_course_type_display')
class Meta:
model = Course
fields = ["id",'name',"course_img","course_type","brief","level","period"]
# depth=2 用深度帮你进行跨表
class CourseDetailViewSerializers(serializers.ModelSerializer):
'''
详细课程的序列化
'''
#一对一的反向查询,fk,choice都是用source,只能取一条或者是一个数据
name = serializers.CharField(source='course.name')
img = serializers.CharField(source='course.course_img')
content = serializers.CharField(source='course.brief')
id = serializers.CharField(source='course.id')
#多对多的,用SerializerMethodField(),可以获取多个
#推荐课程
recommend_courses = serializers.SerializerMethodField()
#老师
teachers= serializers.SerializerMethodField()
# 每个时间段的价格
price = serializers.SerializerMethodField()
# 课程大纲
course_detail = serializers.SerializerMethodField()
#课程章节
coursechapter = serializers.SerializerMethodField()
#
# #课时目录
# coursesection = serializers.SerializerMethodField()
# 推荐课程
def get_recommend_courses(self,obj): #obj--> CourseDetail.obj
# obj.recommendcourse.all()
return [{"id":item.id,"title":item.name}for item in obj.recommend_courses.all()]
def get_teachers(self,obj): #obj--> CourseDetail.obj
# obj.recommendcourse.all()
return [{"id":item.id,"name":item.name,"title":item.title}for item in obj.teachers.all()]
# 每个时间段的价格
def get_price(self,obj):
return [{"valid_period":item.get_valid_period_display(),"price":item.price}for item in obj.course.price_policy.all()]
def get_course_detail(self,obj):
return [{"title":item.title,"content":item.content}for item in obj.courseoutline_set.all()]
def get_coursechapter(self,obj):
return [{"chapter":item.chapter,"title":item.name,"summarys":item.summary}for item in obj.course.coursechapters.all()]
# def get_coursesection(self,obj):
# return [{"chapter":item.chapter,"name":item.name,"summary":item.summary}for item in obj.course.coursechapters.coursesections.all()]
class Meta:
model = CourseDetail
fields = ["id","name","img","hours","teachers","content",'why_study',"what_to_study_brief",
"career_improvement","prerequisite","recommend_courses","price",'course_detail','coursechapter',]
class TestView(APIView):
def get(self, request, *args, **kwargs):
# 序列化,将数据库查询字段序列化为字典
data_list = models.UserInfo.objects.all()
ser = CourseDetailViewSerializers(instance=data_list, many=True)
# 或
# obj = models.UserInfo.objects.all().first()
# ser = CourseDetailViewSerializers(instance=obj, many=False)
return Response(ser.data)
def post(self, request, *args, **kwargs):
# 验证,对请求发来的数据进行验证
print(request.data)
ser = CourseDetailViewSerializers(data=request.data)
if ser.is_valid():
print(ser.validated_data)
ser.save() #直接保存到数据库,在这里会调用GoodSerializer的create方法。
else:
print(ser.errors)
return Response('POST请求,响应内容')
6. 分页
a根据页码进行分页
from rest_framework.views import APIView
from rest_framework import serializers
from .. import models
from rest_framework.pagination import PageNumberPagination
class StandardResultsSetPagination(PageNumberPagination):
# 默认每页显示的数据条数
page_size = 1
# 获取URL参数中设置的每页显示数据条数
page_size_query_param = 'page_size'
# 获取URL参数中传入的页码key
page_query_param = 'page'
# 最大支持的每页显示的数据条数
max_page_size = 1
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = "__all__"
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
b位置和个数进行分页
from rest_framework.views import APIView
from rest_framework import serializers
from .. import models
from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination
class StandardResultsSetPagination(LimitOffsetPagination):
# 默认每页显示的数据条数
default_limit = 10
# URL中传入的显示数据条数的参数
limit_query_param = 'limit'
# URL中传入的数据位置的参数
offset_query_param = 'offset'
# 最大每页显得条数
max_limit = None
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = "__all__"
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
views.py
7. 路由系统
a. 自定义路由
from django.conf.urls import url, include
from web.views import s11_render
urlpatterns = [
url(r'^test/$', s11_render.TestView.as_view()),
url(r'^test.(?P<format>[a-z0-9]+)$', s11_render.TestView.as_view()),
url(r'^test/(?P<pk>[^/.]+)/$', s11_render.TestView.as_view()),
url(r'^test/(?P<pk>[^/.]+).(?P<format>[a-z0-9]+)$', s11_render.TestView.as_view())
]
b. 半自动路由
from django.conf.urls import url,include
from django.contrib import admin
from app01 import views
url(r'^api/(?P<version>w+)/bookviewset/$', views.BookViewSet.as_view({"get":"list","post":"create"}),name='bookviewset'),
url(r'^api/bookviewset/$', views.BookViewSet.as_view({"get":"list","post":"create"}),name='bookviewset'),
# url(r'^bookviewset/(?P<pk>d+)$', views.BookViewSet.as_view({"get":"retrieve","post":"update"}), name='bookviewsetdetail'),
c. 全自动路由
from django.conf.urls import url,include
from rest_framework import routers
from app01 import views
router = routers.DefaultRouter()
router.register("bookviewset",views.BookViewSet)
url(r'',include(router.urls)),
#自动的帮你生成4个url
#^bookviewset/$ [name='book-list']
#^bookviewset.(?P<format>[a-z0-9]+)/?$ [name='book-list'] .json?format=json
#^bookviewset/(?P<pk>[^/.]+)/$ [name='book-detail']
#^bookviewset/(?P<pk>[^/.]+).(?P<format>[a-z0-9]+)/?$ [name='book-detail']
8. 视图
根据继承的类的不同有不同的形式
class View(object):
class APIView(View):
class GenericAPIView(views.APIView):
class GenericViewSet(ViewSetMixin, generics.GenericAPIView)
# ViewSetMixin 是对as_view里面的参数进行处理
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
ViewSetMixin
url(r'^api/(?P<version>w+)/bookviewset/$', views.BookViewSet.as_view({"get":"list","post":"create"}),name='bookviewset')
没有继承ViewSetMixin 这个类的话get请求就是执行对应的get的方法。
ModelViewSet把上面的增删改查都包含了,减少了我们自己写麻烦。
from app01.forms import TokenAuth,SVIPPermisson #解析器 from rest_framework.parsers import JSONParser from rest_framework.parsers import FormParser from rest_framework.parsers import MultiPartParser #版本 from rest_framework.versioning import URLPathVersioning,QueryParameterVersioning class BookViewSet(viewsets.ModelViewSet): authentication_classes=[TokenAuth,] #认证组件 # permission_classes = [SVIPPermisson,] #权限组件 # throttle_classes = [] #频率组件 # parser_classes = [JSONParser,FormParser,MultiPartParser] #解析器,即按照content-type的不同来处理数据 queryset = Book.objects.all() # 取的数据 print(queryset) serializer_class = BookModelSerializers # 序列化器 pagination_class = MyPageNumberPagination #分页器
10. 渲染器
根据 用户请求URL 或 用户可接受的类型,筛选出合适的 渲染组件。
a. json
访问URL:
- http://127.0.0.1:8000/test/?format=json
- http://127.0.0.1:8000/test.json
- http://127.0.0.1:8000/test/
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework.renderers import JSONRenderer
from .. import models
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = "__all__"
class TestView(APIView):
renderer_classes = [JSONRenderer, ]
def get(self, request, *args, **kwargs):
user_list = models.UserInfo.objects.all()
ser = TestSerializer(instance=user_list, many=True)
return Response(ser.data)
b. 表格
访问URL:
- http://127.0.0.1:8000/test/?format=admin
- http://127.0.0.1:8000/test.admin
- http://127.0.0.1:8000/test/
from rest_framework.renderers import AdminRenderer
renderer_classes = [AdminRenderer, ]
c. Form表单
访问URL:
- http://127.0.0.1:8000/test/?format=form
- http://127.0.0.1:8000/test.form
- http://127.0.0.1:8000/test/
from rest_framework.renderers import HTMLFormRenderer renderer_classes = [HTMLFormRenderer, ]
等更多渲染器的形式点击查看
完整全面的内容点击
# URLPathVersioning 是把版本放在url上;最常用的
# QueryParameterVersioning 是把版本当做get的参数 ?version