使用django rest framework开发api

学过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

一、用户登录认证(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<version>[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")

七、视图类+routers自动生成接口路由

从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-extensionsfrom 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',
]
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值