学过python web开发的朋友,肯定用过django框架。它满足界内web开发公认的MVC思想(分工,解耦)。而django更专注于全栈开发,你除了编写后端视图,还需要把数据render到前端模板,在模板中显示数据。说实话,要想成为全栈工程师,有较大难度。所以,当你前后端通吃吃不消时,你就可以挑一端来深入研究,尤其是现在的大中型公司都采用前后端分离的工作模式(并不代表搞后端的前端可以一概不知,反过来只搞前端也不可能后端一概不知),没有后端工程师不会发ajax请求的,更没有前端工程师不懂服务器的。
前后端分离模式
:
可以先了解一下restful接口规范,我觉得restful一句话就能说明白,按http请求定义的面向资源的url
。前后端分离模式下后端只负责返回json数据,前端只负责用ajax请求后端api拿json数据再通过js加载到html标签中显示。其实我们使用django一样可以完成api开发,但是很繁琐。有没有一套组件能帮助我们快速开发api
接口呢?这就是今天的主角——django rest framework。drf框架是建立在django框架的基础之上。用来简化django开发api的组件。既然drf能让api的开发变得简单,那必然是封装了一套又一套新颖实用的组件。本文将围绕以下几个组件展开阐述:用户登录认证(jwt)
,用户权限
,接口限流(防爬虫)
,解析器
,序列化器
,分页
,视图
,路由
,缓存
,版本控制
。
pip install djangorestframework
pip install djangorestframework-jwt
有些api需要用户登录之后才能访问,有些不需要登录也能访问。jwt采用加盐非对称加解密,一层套一层,服务器无需存储即可判断此次携带token是否和起始token一致,比老一套的基于token认证或cookie+session认证好太多,还能防止csrf攻击,这里有一篇写得很好的文章。为了完成用户认证,首先你必须要来一张UserInfo表,只需用户名和密码字段,迁移生成表。然后手动加条记录进去模拟注册成功后的用户。
models.py
class UserInfo(models.Model):
username = models.CharField(max_length=10,verbose_name='姓名')
password = models.CharField(max_length=10,verbose_name='密码')
1、定义jwt的两个方法用于生成加密token和判断token是否合法:
import jwt
import datetime
from jwt import exceptions
SALT = 'iv%x6xo7l7_u9bf_u!9#g#m*)*=ej@bek5)(@u3kh*72+unjv='
def create_token(user_id,username):
"""生成token"""
headers = {
'typ': 'jwt',
'alg': 'HS256'
}
# 构造payload
payload = {
'user_id': user_id, # 自定义用户ID
'username': username, # 自定义用户名
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30) # 超时时间半小时
}
token = jwt.encode(payload=payload, key=SALT, algorithm="HS256", headers=headers).decode('utf-8')
return token
def get_payload(token):
"""token校验"""
result = {'status': False, 'data': None, 'error': None}
try:
verified_payload = jwt.decode(token, SALT, True)
result['status'] = True
result['data'] = verified_payload
except exceptions.ExpiredSignatureError:
result['error'] = 'token已失效'
except jwt.DecodeError:
result['error'] = 'token认证失败'
except jwt.InvalidTokenError:
result['error'] = '非法的token'
return result
看看drf内置的认证类,一共有五个可以被继承,我们选择继承BasicAuthentication来实现用户认证:
2、定义用户登录认证类继承BasicAuthentication:
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from api.models import UserInfo
from api.utils.jwt_createa_and_verified import get_payload
class Authtication(BaseAuthentication):
"""用户认证类,继承BaseAuthentication重写authenticate方法来进行认证"""
def authenticate(self,request):
token = request.query_params.get('token')
result= get_payload(token)
# 认证失败
if not result['status']:
raise exceptions.AuthenticationFailed(result)
# 如果想要request.user等于用户对象,此处可以根据payload去数据库中获取用户对象。
user = UserInfo.objects.filter(id=result.get('data').get('user_id')).first()
request.user = user
# 一个给request.user,一个给request.auth
return (request.user,token)
3、下面是把刚刚这个登录认证类添加到全局,即所有视图的接口都必须通过登录认证才能访问:(rest框架的全局配置都放在REST_FRAMEWORK 这个字典里,你可以在不需要认证的视图中用authentication_classes = []的方式来覆盖掉这个全局配置,表示该视图不进行用户登录认证。)
REST_FRAMEWORK = {
# 认证
'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.auth.Authtication', ],
}
4、编写登录视图,用于成功登录后返回token给前端:(登录接口肯定是不能有登录认证的)
class AuthView(APIView):
"""我的认证类是添加到全局代表所有接口必须登录才能访问,根据继承的搜索顺序,我可以把这个置为空,代表该登录接口不进行用户认证"""
authentication_classes = []
def post(self,request,*args,**kwargs):
user = request.data.get('username')
pwd = request.data.get('password')
try:
user_obj = UserInfo.objects.filter(username=user,password=pwd).first()
if not user_obj:
return JsonResponse({'code':1002,'msg':'用户名或密码错误'})
# 登陆成功我就给你jwt形式的token,下次访问你需要带上token才能访问要求登录后才能访问的接口
token = create_token(user_obj.id,user_obj.username)
# 用户登录成功,返回这个用户的token
return JsonResponse({'code':1000,'msg':'登录成功','token':token})
except:
return JsonResponse({'code':1001,'msg':'用户登陆异常'})
以上做完以后我们就完成了登录认证功能,登录认证流程:
增删查改接口肯定是会在各自的类试图里边,请求进来,过中间件,再路由匹配先找到as_view,往前找找到APIView里的as_view,因为是前后端分离(前端不可能知道你的csrf_token),这里边是返回了一个免除csrf验证的view,并且还执行了super().as_view,再往上找到View的as_view,这里边调用了dispatch,再回头从视图类开始找,找到了APIView中的dispatch。drf丰富的功能就是在APIView里的dispatch中完成的,以前View里的dispatch只是做了个反射。
drf的用户认证类的返回值有三种情况
None
,认证通过。返回一个元组
,认证通过,第一个是当前用户会自动给request.user,第二个给request.auth,供视图中调用这两个属性。抛异常
raise exception.AuthenticationFailed(‘用户认证失败’)原理跟用户认证是一样的,也是看源码,分析出drf内置的权限类,然后继承基类,重写权限判断方法,不过权限是基于登录认证成功后的用户,不然权限是毫无意义的。 现在需要加一个user_type字段(枚举类型)进刚才的UserInfo表,我定义了两个权限类,一个判断是否是SVIP(user_type=3),一个判断是否是VIP(user_type=2)。
权限的内置类:
1、定义权限类继承BasePermission,重写has_permission方法:
from rest_framework.permissions import BasePermission
class SVIPPermission(BasePermission):
"""SVIP鉴权类"""
message = "必须是svip才能访问" # 自定义错误信息
def has_permission(self,request,view):
"""这个request.user就是自定义认证类返回的元组中第一个值,我们这儿就能使用上request.user当前登录用户"""
if request.user.user_type != 3:
return False
# 就返回是否有权限就行了不用抛异常.
return True
class VIPPermission(BasePermission):
"""VIP鉴权类"""
message = "必须是vip以上的用户才能访问" # 自定义错误信息
def has_permission(self,request,view):
if request.user.user_type < 2:
return False
return True
2、把权限类加到全局配置,表示所有视图都要走权限类(我这儿只加VIP权限类,即所有接口都要求用户的user_type字段值大于2才能访问接口。)
REST_FRAMEWORK = {
# 认证类
'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.auth.Authtication', ],
# 权限类
'DEFAULT_PERMISSION_CLASSES': ['app01.utils.permission.VIPPermission'],
}
3、局部会覆盖全局的配置,并且把认证和权限加到全局肯定是不合理的,很多视图的接口无需登录无需权限也可以访问。所以我们应该每次都手动加进需要认证和权限的接口中,像这样。
class GoodsView(APIView):
"""对商品进行操作的视图"""
authentication_classes = [] # 无需登录,把认证类置为空列表,如果有需要的视图就单独加
permission_classes = [] # 无需权限普通用户也能访问,把权限类置为空列表,如果有需要的视图就单独加
def get(self,request)
"""返回商品列表"""
pass
权限类的返回值就只有两种情况:True有权访问,False无权访问,没必要像认证一样去搞抛异常和返回用户相关的值。
这是drf内置的所有限流类,我们写的限流类建议继承SimpleRateThrottle,实现限流更简单。
1、编写的限流类继承SimpleRateThrottle(自动帮我们实现获取用户ip,做访问限制以及提示还有多少秒才能访问接口功能)。
class VisitLimit(SimpleRateThrottle):
"""游客限流,频率读配置文件中的AnonVisitor"""
scope = "AnonVisitor"
def get_cache_key(self,request,view):
return self.get_ident(request)
class UserLimit(SimpleRateThrottle):
"""登录用户限流,频率读配置文件中的UserVisitor"""
scope = "UserVisitor"
def get_cache_key(self,request,view):
return self.get_ident(request)
在全局把刚写的两个限流类加进REST_FRAMEWORK字典中,并且加上具体方案:
# 限流(匿名用户和登录用户我不能全写在全局,那样两种用户频率会出问题)
'DEFAULT_THROTTLE_CLASSES': ['app01.utils.current_limiting.UserLimit', 'app01.utils.current_limiting.VisitLimit'],
# 限流频率设置(匿名用户每分钟最多访问4次,登录用户每分钟10次)
'DEFAULT_THROTTLE_RATES': {"AnonVisitor": '4/m', "UserVisitor": '10/m'},
有时api会不断迭代,就需要添加版本控制,一般在url中传参而不会以查询参数或子域名,请求头传参。需要配置路由以及配置几个参数。
项目主路由配置:
# 表示只有v1或v2来选择,+表示匹配一次或多次。
re_path('^api/(?P[v1|v2]+)/' , include('app01.urls')),
在REST_FRAMEWORK字典中加入如下配置:(就代表全局配置,以后需要在路由中带上版本)。
# 以url传参方式
'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning',
# 不传的话默认v1版本
'DEFAULT_VERSION':'v1',
# 允许版本
'ALLOWED_VERSIONS':['v1','v2'],
# 传参方式
'VERSION_PARAM':'version',
django中只要是有请求体的请求,request.body都会有值。而request.POST要有值,必须要求请求头的content-type为x-www-form-urlencoded且数据形式为{‘a’:1,‘b’:2}。内部自动转化为a=1&b=2且为二进制进行传递。
所以,当request.POST拿不到前端数据时,就手动用request.body拿来看看是个什么格式,如果是
json的话就loads一下。但是在drf中,就好办得多。先看看django rest framework内置的解析器:
# 基类
class BaseParser:...
# 能解析请求头为json/application的数据
class JSONParser(BaseParser)...
# 能解析请求头为x-www-form-urlencoded
class FormParser(BaseParser):...
# 能解析请求头的content-type为multipart/form-data的数据
class MultiPartParser(BaseParser):...
# 能解析文件类型的数据
class FileUploadParser(BaseParser):...
如果我把这些全加进全局(REST_FRAMEWORK)配置。就不会担心跟前端的交互问题了,无论你的content-type是什么我都能解析,然后把数据统一给到request.data(肯定解析是针对到body的请求,GET请求不存在取不到值的问题)。drf的data等于django的POST + FILES,drf的query_params等于django的GET
。
"DEFAULT_PARSER_CLASS":['rest_framework.parsers.JSONParser','rest_framework.parsers.FormParser','rest_framework.parsers.FileUploadParser'],
序列化组件是django rest framework的重头戏,前面讲的认证,权限,版本,限流,解析器基本配置在全局就不需要管了,只有认证和权限会时不时的在视图中改一下,因为有些视图访问要求不一样嘛。以后想要数据交互,写的最多的就是序列化类(器)。那什么叫序列化?一句通俗的话就是将每条数据(不管是不是可序列化类型)都能统一的序列化为json对象来传输给前端。
先来看看在django中开发api怎样序列化数据为json:
# CBV免除csrf验证(这是django,drf中不需要,drf的as_view一进来就做了这一步)
@method_decorator(csrf_exempt,name='dispatch')
class GoodsView1(View):
def get(self, request):
# 只取前三条就行了
goods = Goods.objects.all()[:3].values('id','name','image','datetime')
# 转化为json能序列化的数据类型
goods = list(goods)
# 如果序列化的对象非字典,要加safe=False
return JsonResponse(goods,safe=False)
根据上面代码你需要通过values把数据取出来,然后转化为能被json序列化的类型(字典,列表,元组,字符串,整型,浮点型,布尔型,还有None
),django中也是有专门的序列化器serializer的,主要原因是django的序列化器不能序列化外键关系,并且还有很多功能不人性化,比如url地址不会自动补全(前后端分离需要提供完整url,前端可能是手机,pad,PC,压根儿不在一个站点,这也正是django rest framework中请求一进来重写as_view马上免除csrf_token认证的原因)。
那drf中怎样利用序列化组件序列化数据呢?
直接说有点懵,可以拿它和form组件做比较。以前基于django的前后端不分离模式
:form组件能轻松完成前端html标签自动生成和用户提交数据的校验。现在是前后端分离
,后端只返回json数据,以前的render再也不会用了,所以drf的序列化serializer就是针对json数据的序列化和校验。他们二者是取代关系,全栈form组件不可少,api后端serializer不可少,他们使用起来很像很像。
我总结了一下form组件和DRF的serializer组件的异同:
a、form组件用于全栈,生成待用户输入的表单且校验用户输入的数据,sererializer组件用于json数据的正序和反序,也是后端到前端,前端到后端。
b、都是可以继承两个类,Form和ModelForm,Serializer和ModelSerializer。
c、两个组件使用的第一步都是先定义类(继承Model类的话字段是覆盖效果),再实例化使用。
d、form组件继承ModelForm类才有save()方法,而serializer组件继承两种类都可以调save便捷地完成记录创建。
models文件:
class UserInfo(models.Model):
username = models.CharField(max_length=10,verbose_name='姓名')
password = models.CharField(max_length=10,verbose_name='密码')
class People(models.Model):
name = models.CharField(max_length=10, blank=True, default='', verbose_name="姓名")
age = models.CharField(max_length=10, blank=True, default='', verbose_name="年龄")
sex = models.BooleanField(default=True, verbose_name="性别")
user = models.ForeignKey(UserInfo,blank=True,null=True,on_delete=models.CASCADE,related_name='people')
class Goods(models.Model):
name = models.CharField(max_length=10,blank=True,default='',verbose_name="商品名称")
datetime = models.DateTimeField(verbose_name="当前时间",auto_now=True)
image = models.FileField(upload_to='goods/%Y/%m',verbose_name="商品图片")
people = models.ForeignKey(People,blank=True,null=True,on_delete=models.CASCADE,related_name='goods')
1、先定义序列化类:(继承Model型就需要指定Model和fields)
class PeopleSerializer(ModelSerializer):
class Meta:
model = People
fields = "__all__"
class GoodsSerializer(ModelSerializer):
class Meta:
model = Goods
fields = "__all__"
2、视图中使用方法很简单,只需实例化序列化类对象,把待返回的对象集传进去(如果是多条必须指定many=True),然后返回实例对象的data即可。
class GoodsView1(APIView):
"""商品列表不需要登录和权限就能访问,限流为全局配置的游客频率每分钟10次"""
authentication_classes = []
permission_classes = []
throttle_classes = [VisitLimit, ]
def get(self,request):
goods = Goods.objects.all()[:3]
serializer = GoodsSerializer(goods,many=True)
return Response(serializer.data)
我们可以给GoodsSerializer加上一句话,把people外键改写:
class GoodsSerializer(ModelSerializer):
people = PeopleSerializer()
class Meta:
model = Goods
fields = "__all__"
有人说万一我的people表还有一层外键,该怎样,那就还要定义一个外键的serializer类,然后把people的外键改写。效果如下:
序列化器类显示枚举类,处理多对多,一对多都要重新改写一下序列化字段,自定义返回值SerializerMethodField类型,它会自动调用钩子函数get_xxx,跟字段对应的钩子方法。
class UserInfoSerializer(ModelSerializer):
# 枚举剔出来定义,这样才能显示数据而不是1,2,3
user_type = serializers.CharField(source='get_user_type_display')
# 多对多,也剔出来,用source做不到那个力度,因为进去还要遍历。只能用自定义的函数。
roles = serializers.SerializerMethodField()
# 这个users_query就是你在视图实例化(使用)这个类时,传入给instance的值
def get_roles(self, users_query):
# 这儿自定义返回信息,是一个列表生成式做遍历
return [{'id':role.id,'title':role.title} for role in users_query.roles.all()]
class Meta:
model = UserInfo
fields = ['username','password','user_type','roles','group']
# 给某个字段增加或修改参数
extra_kwargs = {'group':{'min_length':6},}
说了这么多也是后端返回数据给前端,那前端来的数据怎样反序列化呢?那到底怎样才能证明我是django rest framework的视图,我可以使用drf的功能?一句话,只需要你的视图继承APIView类或APIView的子类。
3、下面将开始反序列化,即前端数据到后端,反序列化的同时肯定会进行数据校验,这和django中的form组件是一个道理。
class GoodsSerializer(ModelSerializer):
people = PeopleSerializer()
# 覆盖hobby字段,显示出详情
hobby = serializers.SerializerMethodField()
class Meta:
model = Goods
fields = "__all__"
def get_hobby(self,obj):
return obj.get_hobby_display()
# TODO:对单个字段验证,这是钩子函数,就跟form组件的钩子是一样的
def validate_name(self, value):
if "python" in value.lower():
return value
raise serializers.ValidationError('输入的商品名称必须包含python')
# 除此之外,还有自定义钩子,全局钩子。
这就是简单的增删查改接口,其中增,改需要进行反序列化和校验:
class GoodsView1(APIView):
authentication_classes = []
permission_classes = []
throttle_classes = [VisitLimit, ]
def get(self,request):
"""查所有数据或(如果带有id证明是查一个)"""
id = request.query_params.get('id')
if not id:
goods = Goods.objects.all()
serializer = GoodsSerializer(goods,many=True)
else:
goods_obj = get_object_or_404(Goods,id=id)
serializer = GoodsSerializer(goods_obj)
return Response(serializer.data)
def post(self,request):
"""增接口"""
data = request.data
# 只需用data关键字把待反序列化的数据传进去
serializer = GoodsSerializer(data=data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
def delete(self,request):
"""删接口,查询单个和这个删除以及更新接口我都是?id=1这样传参,可以统一接口url,如果在url传参路由就会要重新定义"""
id = request.query_params.get('id')
if id:
book_obj = get_object_or_404(Goods,id=id)
book_obj.delete()
return Response("删除成功")
return Response("您必须传入待删除的id,不能一次性全删")
def patch(self,request):
"""更新接口"""
id = request.query_params.get('id')
if id:
book_obj = get_object_or_404(Goods, id=id)
# instance是待修改的对象,data是修改的内容
serializer = GoodsSerializer(data=request.data, instance=book_obj)
if serializer.is_valid():
serializer.save()
# 更新成功后返回更新的这条记录
return Response(serializer.data)
return Response(serializer.errors)
return Response("您必须传入待更新的id")
从django rest framework一路走来到这儿,我们视图至始至终都在继承APIView,现在我想让接口编写更简单,django rest framework中封装了很多类,可以让你的代码更简单,但功能却越强大。现在我把django rest framework强大的视图类分为了三个体系:
1、mixins体系
(自动化实现增删改查,该体系是个裸功能,没有任何父类,仅仅是帮你编写了五个接口——增删改查查),一句话你只要继承了CreateModelMixin,只需要给出返回的数据和序列化类,不需要你编写post接口,但是视图只继承mixins体系是不行的,没有路由。
2、generics体系
(这里面除了第一个类都继承了mixins中的功能,意思就是实现了接口但是接口没办法被访问。而第一个类就是为了做路由映射,所以其余类必须继承GenericAPIView来获得as_view的权利。因为View到APIView再到GenericAPIView都是单继承,所以有as_view功能的类最底层就是这个GenericAPIView,generics体系可以理解为让从mixins体系出来的接口能被访问)
3、viewsets体系
自动生成路由,既然mixins体系是裸功能,证明肯定有个自动生成路由的体系,就是routers组件。这里你只需要看GenericViewSet
这个类,它很牛逼,它就是用来自动生成路由的,既然这个路由能自动生成(只需要配置一点代码),所以generics体系是可以不使用的。
梳理:三个体系,你需要组合来继承定制你的接口功能。python是支持多继承,那到底怎样继承合适呢?如果你的接口要有增删改查接口就继承ModelViewSet(看上图,它除了增删改查查还继承了自动生成路由的GenericViewSet);如果你视图只完成增删接口,就可以混合双打,继承mixins体系里五个类中的CreateModelMixin和DestroyModelMixin,再搭配继承GenericViewSet,比如上图的ReadeOnlyModelViewSet就是它只要查(单个)查(所有)接口),然后自动生成这两个路由。
我前面硬写的增删查改四个接口,现在我先继承generic体系的增删改查查的五个类:
class GoodsView1(ListAPIView,CreateAPIView,RetrieveAPIView,DestroyAPIView,UpdateAPIView,):
# 就继承这几个类,我就省去了自定义的四个接口
authentication_classes = []
permission_classes = []
throttle_classes = [VisitLimit, ]
queryset = Goods.objects.all() # 指明你的待序列化数据(默认是全部)
serializer_class = GoodsSerializer # 指明你的序列化类
可以看到五个接口都在,我现在不使用 generic体系,我使用mixins体系加viewsets体系来自动生成路由:
class GoodsView1(CreateModelMixin,ListModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin,GenericViewSet):
authentication_classes = []
permission_classes = []
throttle_classes = [VisitLimit, ]
queryset = Goods.objects.all() # 指明你的待序列化数据(默认是全部)
serializer_class = GoodsSerializer #
马上报错TypeError: The ’actions‘argument must be provided when calling '.as_view()' on a ViewSet. For example '.as_ view({'get': 'list'})
它说我们没有将get请求和list方法对应起来,原因是我们现在的所有接口是自动定义的,比如请求方法是post,get,内部调用的是create,list等方法,所以他要求我们在as_view里边用字典映射起来。现在我们开始用router自动生成路由。
可以在主路由也可以app中的路由。
router = DefaultRouter()
router.register(r'goods',views.GoodsView1)
urlpatterns = [
# 不要使用path,
re_path(r'', include(router.urls),name='goods'),
]
这四个接口就是自动生成的。它生成的接口数,会自动判断你用的mixins体系中的哪几个接口,本例中我们全继承上了,所以有四个接口。
综上:现在我们的接口可以简洁为:
class GoodsView1(CreateModelMixin,ListModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin,GenericViewSet):
authentication_classes = []
permission_classes = []
throttle_classes = [VisitLimit, ]
queryset = Goods.objects.all() # 指明你的待序列化数据(默认是全部)
serializer_class = GoodsSerializer #
只需指明认证类,权限类,限流类,queryset(你要返回的所有数据集合),序列化类。其中前三个可以用全局配置的。局部可以取代全局。至于你想要哪些接口,你可以自己选择性继承mixins体系再继承一个GenericViewSet自动生成路由,而generic体系可以不用配合其他体系,但是需要手动配置url。
当我们后端不管是sql还是nosql,数据量很大时,肯定不可能全部返回到一个页面,就需要做分页,把数据分页展示到多个页面上,相信你已经使用过django的pagination来分页,但是在drf中,使用分页很简单。先看看drf有哪些内置分页类能让我们继承。
1、定义分页类继承PageNumberPagination普通分页
class MyPageNumberPagination(PageNumberPagination):
"""普通分页类"""
# 每页展示多少条数据
page_size = 1
# 前端可以自己通过修改page=10,取哪一页的数据。
page_query_param = 'page'
# 前端可以?size=10000自己配置,每页想取多少条自己设置
page_size_query_param = 'size'
# 最大页码的查询参数名
max_page_size = 1000
2、作为类变量写进上面的视图中
authentication_classes = [] # 用户登录认证
permission_classes = [] # 用户权限
throttle_classes = [VisitLimit, ] # 用户限流
pagination_class =[MyPageNumberPagination,] # 分页类
每页只显示1条数据:
如果继承CursorPagination的话:
class MyPageNumberPagination(CursorPagination):
"""加密分页,常见于大型网站"""
cursor_query_param = 'page'
page_size = 1
ordering = 'id'
# 我就直接弄得None,所以url设置每页显示多少条数据的参数没有
page_size_query_param = None
max_page_size = None
所以现在面试被问到:当有海量的数据需要你返回时,你会怎么做?我会用加密分页来解决。
pip install drf-extensions
再 from rest_framework_extensions.cache.mixins import CacheResponseMixin
然后让你想做缓存的视图继承这个类即可。至于过期时间你可以在配置文件中再加个REST_FRAMEWORK_EXTENSIONS 字典:
REST_FRAMEWORK_EXTENSIONS = {
# 单位秒,5秒后缓存过期
'DEFAULT_CACHE_RESPONSE_TIMEOUT':5
}
drf默认是用的内存作为缓存位置,你只需配置过期时间,其实缓存的方式很多,还有文件,数据库都是可以的,主要是看你怎样配置配置文件,而我们常用redis作为缓存数据库:这样配置:
先pip install django_redis
# django的缓存配置
CACHES = {
'default':{
'BACKEND': 'django_redis.cache.RedisCache',
# redis无密码------> 'LOCATION': 'redis://120.27.242.29:6379/数据库号',默认是0号数据库
'LOCATION': 'redis://:redis密码@ip地址:6379/5',
'OPTIONS':{
'CLIENT+CLASS':'django_redis.client.DefaultClient',
}
}
}
看看效果:
明显响应时间减低了很多,并且可以看到redis的5号数据库已经有缓存数据了:
1、django的生命周期
:
请求进来先找服务器,通过wsgi(web服务网关接口)协议,跟django框架进行通讯,服务器本质:就是个socket写的服务端。再走中间件,然后反射到接口,要找数据库就找数据库,然后返回应答,最后再走中间件的process_response方法。
2、包含rest_framework的django的请求生命周期
:
区别就是as_view反射的时候,以前继承View是走View的as_view和dispatch,现在是走APIView的as_view和dispatch(免除csrf然后封装了一系列组件)
中间件&装饰器:
中间件:可以做权限,认证,csrf(全局),session(全局),ip黑名单
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
# session也是基于中间件实现的,下面这行注释了的话,request点不出session
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]