Django测试开发学习笔记(三)

restframework

restframework简介

它是基于Django的,帮助我们快速开发符合restful规范的接口框架,它主要适用于前后端分离项目。

文档1:https://www.django.cn/course/show-20.html

文档2:https://www.django-rest-framework.org/

快速入门

  • 利用pycharm新建一个Django项目


    image
  • 安装&配置Django REST framework

    • 在虚拟环境中pip install djangorestframework

      image

    • settings.py文件中配置rest_framework


      image
  • 创建app python manage.py startapp api

  • settings.py文件中配置app


    image
  • 配置主路由 urls.py

    image
  • 创建模型

    api/models.py

    from django.db import models
    
    # Create your models here.
    
    class Project(models.Model):
        """
        项目表
        """
        ProjectType = (
            ('Web', 'Web'),
            ('App', 'App')
        )
        id = models.AutoField(primary_key=True)
        name = models.CharField(max_length=50, verbose_name='项目名称')
        version = models.CharField(max_length=50, verbose_name='版本')
        type = models.CharField(max_length=50, verbose_name='类型', choices=ProjectType)
        description = models.CharField(max_length=1024, blank=True, null=True, verbose_name='描述')
        status = models.BooleanField(default=True, verbose_name='状态')
        LastUpdateTime = models.DateTimeField(auto_now=True, verbose_name='最近修改时间')
        createTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    
        class Meta:
            db_table = "api_project"
    
  • settings.py配置数据库

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'HOST': '127.0.0.1',  # 数据库主机
            'PORT': 3306,  # 数据库端口
            'USER': 'root',  # 数据库用户名
            'PASSWORD': 'admin1234',  # 数据库用户密码
            'NAME': 'drf_demo'  # 数据库名字
        }
    }
    
    
  • 创建序列化器Serializers

    from rest_framework import serializers
    from api.models import Project
    
    class SerializersProject(serializers.ModelSerializer):
    
        class Meta:
            # 进行序列化的model
            model = Project 
            # 展示的字段
            fields = ["id","name","version","type","description","status","LastUpdateTime","createTime"] 
    
  • 数据库迁移

    安装msyql client

    pip install mysqlclient==1.4.6
    

    数据库迁移

    python manage.py makemigrations
    python manage.py migrate api
    
    image
  • 视图

    from rest_framework import viewsets
    
    # Create your views here.
    from api.models import Project
    from api.serializers import SerializersProject
    
    class Projects(viewsets.ModelViewSet):
        queryset = Project.objects.all()
        # 指定序列化器
        serializer_class = SerializersProject
    
    • 不再写多个视图,我们将所有常见行为分组写到叫 ViewSets 的类中。如果我们需要,我们可以轻松地将这些细节分解为单个视图,但是使用viewsets可以使视图逻辑组织良好,并且非常简洁。
  • 配置子路由

from rest_framework import routers
from . import views
router = routers.DefaultRouter()
router.register(r'projects', views.Projects)

urlpatterns = [
 ]
urlpatterns += router.urls
- 因为我们使用的是viewsets而不是views,所以我们可以通过简单地使用路由器类注册视图来自动生成API的URL conf。再次,如果我们需要对API URL进行更多的控制,我们可以简单地将其拉出来使用常规基于类的视图,并明确地编写URL conf。最后,我们将包括用于支持浏览器浏览的API的默认登录和注销视图。这是可选的,但如果您的API需要身份验证,并且你想要使用支持浏览器浏览的API,那么它们很有用。
  • 访问http://127.0.0.1:8000/v01/展示所有v01下的接口

    image
  • 访问http://127.0.0.1:8000/v01/projects/

    image
    image

序列化器

在开发REST API的视图中,虽然每个视图具体操作的数据不同,但增、删、改、查的实现流程基本套路化,所以这部分代码也是可以复用简化编写的:

增:校验请求数据 -> 执行反序列化过程 -> 保存数据库 -> 将保存的对象序列化并返回

删:判断要删除的数据是否存在 -> 执行数据库删除

改:判断要修改的数据是否存在 -> 校验请求的数据 -> 执行反序列化过程 -> 保存数据库 -> 将保存的对象序列化并返回

查:查询数据库 -> 将数据序列化并返回

Django REST framework(DRF) 提供了Serializer可以快速根据 Django ORM 或者其它库自动序列化/反序列化可以帮助我们简化上述部分代码编写,大大提高开发速度。

serializers
  • REST API接口核心任务

    开发REST API接口时,常做的是三件事:

    • 将请求的数据(如JSON格式)转换为模型类对象
    • 操作数据库
    • 将模型类对象转换为响应的数据(如JSON格式)

    而数据格式化转换的操作往往是繁琐且重复的。

  • 序列化Serialization

    为了简化这些操作,引入新的功能:序列化和反序列化。

    1. 序列化

    可将序列化的含义简单理解为:

    将程序中的一个数据结构类型转换为其他格式(字典、JSON、XML等),例如将Django中的模型类对象装换为JSON字符串,这个转换过程我们称为序列化。

    比如在一个查询数据的API中,我们查询数据库数据得到模型类对象,将其按照格式重组并转换为json的过程就是序列化:

    from django.views.generic.base import View
    from .models import Project
    
    class ProjectList(View):
    
        def get(self,request):
            # 数据库查询获取模型类对象的QuerySet
            projects = Project.objects.all()
            # 序列化,把对象转换成字典
            # 创建一个空列表,存储序每个项目描述字典
            pro_list = []
            for i in projects:
                pro = {
                    "id":i.id,
                    "name":i.name,
                    "version":i.version,
                    "type":i.type,
                    "description":i.description,
                    "status":i.status,
                    "user_id":i.user.id
                }
                pro_list.append(pro)
            # 使用JsonResponse把python列表或者字典转换成json串并返回给前端,注意列表safe值必须为false
            return JsonResponse(pro_list,safe=False)
    
    1. 反序列化

    相反的,可将反序列化的含义简单理解为:

    将其他格式(字典、JSON、XML等)转换为程序中的数据,例如将JSON字符串转换为Django中的模型类对象,这个过程我们称为反序列化。

    比如在一个新增数据的API中,我们接收前端传递来的json数据,将其转换为模型类对象的过程就是反序列化:

    from django.views.generic.base import View
    from .models import Project
    
    class ProjectList(View):
        def post(self,request):
            # 获取到前端传过来的json串
            json_data = request.body
            # 使用json.loads把json串转换成python字典或者列表
            data = json.loads(json_data)
            # 反序列化
            # 把python字典进行转换成模型类对象
            pro = Project()
            pro.version = data["version"]
            pro.name = data["name"]
            pro.ProjectType = data["type"]
            pro.description=data["description"]
            pro.status=data["status"]
            pro.user = User.objects.filter(username=data["username"]).first()
            # 使用bulk_create方法把数据插入到数据库中
            Project.objects.bulk_create([pro])
    
            return HttpResponse("添加成功")
    

    可以看出,在开发REST API接口时,我们在视图中需要做的最核心的事是:

    • 将数据库数据序列化为前端所需要的格式,并返回;
    • 将前端发送的数据反序列化为模型类对象,并保存到数据库中。
  • Serializer序列化器的使用

    接下来我们使用restframework框架提供的序列化器来帮我们实现序列化和反序列化的操作。

    Serializer类有两种,一种是基于Serializer类的基本序列化器,另一种是封装度更高,使用更简单的模型类ModelSerializer序列化器,其实为了效率,我们更多使用的是ModelSerializer类。

    • 基于Serializer类的序列化器

      • 首先新建一个serializers.py文件。

      使用序列化基类实现Project模型类的序列化器:

      在app中创建新文件serializers,在其内定义序列化器,继承于Serializer类。

      image
      • 定义Serializer序列化器
      # 导入模块
      from rest_framework import serializers
      # 导入模型
      from v02.models import Project
      
      # 继承Serializer类
      class SerializersProject(serializers.Serializer):
          # 定义序列化器,字段须与模型中各个字段相同
          # read_only只读
          id = serializers.IntegerField(read_only=True)
          # required必填,min_length最小长度,max_length最大长度,先校验序列化器的,再校验模型定义的
          name = serializers.CharField(required=True,min_length=4,max_length=40)
          # default默认值
          version = serializers.CharField(max_length=50,default='v0.1')
          type = serializers.CharField(max_length=50,default='Web')
          # allow_blank允许为空,allow_null允许为null
          description = serializers.CharField(max_length=1024, allow_blank=True,allow_null=True)
          status = serializers.BooleanField(default=True)
          LastUpdateTime = serializers.DateTimeField(read_only=True)
          createTime = serializers.DateTimeField(read_only=True)
      
      • 字段与参数


        image
      • 序列化器使用

        在视图中使用serializer对象直接构建序列化数据

        # Create your views here.
        from v02.models import Project
        from v02.serializers import SerializersProject
        
        # 使用drf提供的基础视图APIView,继承自django的View
        class Projects(APIView):
            # 序列化器的使用代码示例
            def get(self,request):
        
                projects = Project.objects.all()
                # 指定序列化器,instance表示实例,many=True表示格式为列表,不写表示格式为json
                serializer = SerializersProject(instance=projects,many=True)
                # drf提供的Response方法
                return Response(serializer.data)
               
             # 反序列化器的使用代码示例
            def post(self,request):
                data = request.data # 等同于 request.POST request.body
                serializer = SerializersProject(data=data)
                # is_valid按照定义的序列化器校验数据,raise_exception=True表示如果有问题时会抛出异常
                serializer.is_valid(raise_exception=True)
                # 保存数据
                serializer.save()
                # validated_data经过验证的数据
                return Response(serializer.validated_data)
        

        many参数:如果关联的对象数据不是只有一个,而是包含多个数据,需要使用many=True指定。

    - 反序列化使用
    ```py
    # 在视图中继续添加下面方法
    # 反序列化器的使用代码示例
          def post(self,request):
              data = request.data # 等同于 request.POST request.body
              serializer = SerializersProject(data=data)
              # is_valid按照定义的序列化器校验数据,raise_exception=True表示如果有问题时会抛出异常
              serializer.is_valid(raise_exception=True)
              # 保存数据
              serializer.save()
              # validated_data经过验证的数据
              return Response(serializer.validated_data)
    ```
    ![image](https://upload-images.jianshu.io/upload_images/12041448-98a0008d9f706024.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    ![image](https://upload-images.jianshu.io/upload_images/12041448-8fd4c981df14a4e4.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
ModelSerializer

相比于Serializer类序列化器,DRF还提供了一个更加深度封装的ModelSerializer模型类序列化器,可以帮助我们更快的、傻瓜式的创建一个Serializer类。

  • ModelSerializer与常规的Serializer相同,但提供了:

    • 基于模型类自动生成一系列字段(可以不再自己定义字段)
    • 基于模型类自动为Serializer生成validators,比如unique_together()
    • 包含默认的create()和update()的实现(可以不再自己定义update和create方法)
  • 使用ModelSerializer定义Serializer序列化器

    不再继承于serializers.Serializer类,而是继承模型序列化器类serializers.ModelSerializer,无需再自定义字段。

    serializer.py

    # 继承ModelSerializer类
    class ModelSerializerProject(serializers.ModelSerializer):
        class Meta:
            # 指定要序列化的model
            model = Project
            # 指定要序列化的字段
            fileds="__all__" # 所有字段
            # fileds=[] 选择字段
    

    view.py

    把SerializersProject改为serializers.ModelSerializerProject,其他地方不变

    from rest_framework.response import Response
    from rest_framework.views import APIView
    from v02.models import Project
    from v02 import serializers
    class Projects(APIView):
    
        # 序列化器的使用
        def get(self,request):
    
            projects = Project.objects.all()
            serializer = serializers.ModelSerializerProject(instance=projects,many=True)
            return Response(serializer.data)
    
        # 反序列化器的使用
        def post(self,request):
            data = request.data
            serializer = serializers.ModelSerializerProject(data=data)
            serializer.is_valid(raise_exception=True)
            # 保存数据
            serializer.save()
            # validated_data经过验证的数据
            return Response(serializer.validated_data)
    
    
    image
    image
  • serializer和ModelSerializer的区别


    image
序列化器常用操作
  • ModelSerializer元类属性

    • model指定数据模型

      class ProjectModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = Project # 指定要序列化的模型类,必填属性
      
    • fields 指定序列化字段

      class ProjectModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = Project # 指定要序列化的模型类,必填属性
            fields = '__all__' # 指定要序列化显示的字段,和exclude属性必传一个,当然也可以共存
      
      

      fields取值:

    __all__:表示显示模型类中的所有字段

    可以显示指定:["id","name",…] 或者 ("id","name",…)

    • exclude 指定非序列化字段
    class ProjectModelSerializer(serializers.ModelSerializer):
      class Meta:
          model = Project # 指定要序列化的模型类,必填属性
          fields = '__all__' # 指定要序列化显示的字段,必填属性
          exclude = ['id'] # 指定不参与序列化的字段,同样也可以写成元祖,和fields属性必传一个,当然也可以共存
    
    • read_only_fields指定只读字段
    class ProjectModelSerializer(serializers.ModelSerializer):
      user_id = serializers.IntegerField(source="user.id")  # 使用source指定,
      class Meta:
          model = Project
          fields = "__all__"
          read_only_fields = ['id','LastUpdateTime','createTime'] # 指定只读字段,也就是查询显示,不写入 
    
    • write_only_fields指定只写字段
      
      class ProjectModelSerializer(serializers.ModelSerializer):
      user_id = serializers.IntegerField(source="user.id")  # 使用source指定,
      class Meta:
          model = Project
          fields = "__all__"
          write_only_fields = ['user_id'] # 指定只写字段,也就是查询不显示,创建会写入数据库
      
    • extra_kwargs给字段添加额外参数
    class ProjectModelSerializer(serializers.ModelSerializer):
      class Meta:
          model = Project
          fields = "__all__"
          read_only_fields = ['id','LastUpdateTime','createTime']
          extra_kwargs = { # 使用extra_kwargs可以给字段添加额外的参数,而不用重新定义该字段
              "name":{
                  "required":True,
                  "max_length":40,
                  "min_length":4,
                  "error_messages":{
                    "required":"项目名必填", 
                    "max_length":"项目名不超过40位",
                    "min_length":"项目名不少于4位"
                  }
              }
          }
    
    image
    • depth 查询显示的深度
    class ProjectModelSerializer(serializers.ModelSerializer):
      class Meta:
          model = Project
          fields = "__all__"
          read_only_fields = ['id','LastUpdateTime','createTime']
          extra_kwargs = { # 使用extra_kwargs可以给字段添加额外的参数,而不用重新定义该字段
              "name":{
                  "required":True,
          
              }
          }
          depth = 1 # 定义查询嵌套的深度,最多不能超过10
    
    image
  • 自定义字段

    可以指定模型中不存在的字段,例如查询时获取该项目下用例总数

    class ProjectModelSerializer(serializers.ModelSerializer):
        count = serializers.SerializerMethodField(label='用例数量',read_only=True) # 使用SerializerMethodField定义字段
        def get_count(self,obj):
            '''
            为自定义字段赋值,方法名固定写法,get_字段名,obj代表当前表实例对象
            :param obj: 
            :return: 
            '''
            return 100
    
    
    image
  • 数据验证

    • 使用extra_kwargs进行基本的验证
    class ModelSerializerProject(serializers.ModelSerializer):
    class Meta:
        # 指定要序列化的model
        model = Project
        # 指定要序列化的字段
        fields = '__all__'# 所有字段
        extra_kwargs = {  # 使用extra_kwargs可以给字段添加额外的参数,而不用重新定义该字段
            "name": {
                "required": True,
                "max_length": 40,
                "min_length": 4,
                "error_messages": {
                    "required": "项目名必填",
                    "max_length": "项目名不超过40位",
                    "min_length": "项目名不少于4位"
                }
            }
        }
    
    • Validation自定义验证逻辑(单字段)

      class ModelSerializerProject(serializers.ModelSerializer):
      class Meta:
          # 指定要序列化的model
          model = Project
          # 指定要序列化的字段
          fields = '__all__'# 所有字段
          extra_kwargs = {  # 使用extra_kwargs可以给字段添加额外的参数,而不用重新定义该字段
              "name": {
                  "required": True,
                  "max_length": 40,
                  "min_length": 4,
                  "error_messages": {
                      "required": "项目名必填",
                      "max_length": "项目名不超过40位",
                      "min_length": "项目名不少于4位"
                  }
              }
          }
      # 对name字段自定义一些限制,使用"validate_限制字段()"函数来定义
      def validate_name(self, value):
          # 只能输入字母、汉字、数字,这里如果限制了位数,可以把extra_kwargs对位数的限制去掉
          r = r'^[\da-zA-Z_\u4e00-\u9fa5]{4,40}$'
          if re.match(r,value):
              return value
            
          else: 
              # 抛异常
              raise serializers.ValidationError("项目名称为4-40位字母或汉字")
      

      不符合正则:

      image

      符合正则:


      image
- Validation自定义验证逻辑(多字段)
```py
# 继承ModelSerializer类
class ModelSerializerProject(serializers.ModelSerializer):
# 新增两个字段,用来讲解validate多字段验证
startTime = serializers.IntegerField()
endTime = serializers.IntegerField()

class Meta:
    # 指定要序列化的model
    model = Project
    # 指定要序列化的字段
    fields = '__all__'  # 所有字段
    # fileds=[] 选择字段
    extra_kwargs = {  # 使用extra_kwargs可以给字段添加额外的参数,而不用重新定义该字段
        "name": {
            "required": True,
            "max_length": 40,
            "min_length": 4,
            "error_messages": {
                "required": "项目名必填",
                "max_length": "项目名不超过40位",
                "min_length": "项目名不少于4位"
            }
        }
    }

def validate_name(self, value):
    # 只能输入字母和汉字
    r = r'^[\da-zA-Z_\u4e00-\u9fa5]{4,40}$'
    if re.match(r, value):
        return value
    else:
        raise serializers.ValidationError("项目名称为4-40位字母或汉字")

# 多字段验证
def validate(self, attrs):
    print("attrs:{}".format(attrs))
    startTime = attrs.pop("startTime")
    endTime = attrs.pop("endTime")
    if endTime > startTime:
        return attrs
    else:
        raise serializers.ValidationError("结束时间必须大于开始时间")
```

结束时间大于开始时间:

![image](https://upload-images.jianshu.io/upload_images/12041448-f44238c7fa4047ae.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

结束时间小于开始时间:

![image](https://upload-images.jianshu.io/upload_images/12041448-d3cd5fe91257b578.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  • 关联查询

    使用ORM多表关联时的学习模型:

    # 为了讲解关联查询新增的model
    class Stu(models.Model):
        s_name = models.CharField('学生姓名', max_length=64, null=False, help_text='学生姓名')
        s_sex = models.IntegerField('性别', max_length=1, choices=((0, '男'), (1, '女')), help_text='性别')
        s_age = models.PositiveIntegerField('年龄', null=False, default=0, help_text='年龄')
        s_dept = models.CharField("专业", max_length=64, null=False, help_text="专业")
        si_id = models.OneToOneField(to='StudentInfo', to_field="id", on_delete=models.CASCADE, db_column='si_id',
                                     related_name="stu_stu_info")
        course = models.ManyToManyField(to="Course", db_table="stu_course", related_name="course_student")
    
        class Meta:
            db_table = 'stu'
    
    # 学生信息表
    class StudentInfo(models.Model):
        s_card = models.CharField("身份证号", max_length=18, null=False, unique=True)
        s_phone = models.CharField(max_length=11, null=False, unique=True, help_text="手机号")
        s_addr = models.CharField("家庭住址", max_length=128, help_text="家庭住址")
    
        class Meta:
            db_table = "student_info"
    
    # 课程表
    class Course(models.Model):
        c_name = models.CharField('课程名', max_length=64, null=False, help_text="课程名")
        t_id = models.ForeignKey(to="Teacher", to_field="id", on_delete=models.CASCADE, related_name='teacher_course',
                                 db_column='t_id', help_text="老师编号")
    
        class Meta:
            db_table = "course"
    
    # 成绩表
    class Score(models.Model):
        s_id = models.ForeignKey(to="Stu", to_field="id", on_delete=models.CASCADE, related_name='stu_score',
                                 db_column='s_id', help_text="学生编号")
        c_id = models.ForeignKey(to="Course", to_field="id", on_delete=models.CASCADE, related_name='course_score',
                                 db_column='c_id', help_text="学生编号")
        score = models.PositiveIntegerField("成绩", null=False, default=0, help_text="成绩")
    
        class Meta:
            db_table = "score"
    
    # 老师表
    class Teacher(models.Model):
        t_name = models.CharField("老师姓名", max_length=64, null=False, help_text="老师姓名")
    
        class Meta:
            db_table = "teacher"
    
    • 一对一 (学生表和学生信息表)

    serializers.py

    # 每个model都要创建序列化器
    class ModelSerializerStu(serializers.ModelSerializer):
        phone = serializers.StringRelatedField(source="si_id.s_phone")
    
        class Meta:
            model = Stu
            fields = "__all__"
    
    class ModelSerializerStudentInfo(serializers.ModelSerializer):
        # 没有调试成功,GG
        name = serializers.StringRelatedField(source="stu_stu_info.s_phone")
    
        class Meta:
            model = StudentInfo
            fields = "__all__"
    

    urls.py

      urlpatterns = [
      path('projects/',views.Projects.as_view()),
      path('students/',views.Students.as_view())
    ]
    
    image
    • 多对多 (学生表和课程表)

      serializers.py

      class ModelSerializerStu(serializers.ModelSerializer):
        phone = serializers.StringRelatedField(source="si_id.s_phone")
        # drf的序列化器中没有直接多对多的关系的序列化操作
        # 使用SerializerMethodField自定义字段来实现
        course_name = serializers.SerializerMethodField(read_only=True)
      
        class Meta:
            model = Stu
            fields = "__all__"
      
        def get_course_name(self, obj):
            # 这个学生的所有课程
            courses = obj.course.all()
            # 返回课程名列表
            return [c.c_name for c in courses]
      
      
      image
子序列化 (没懂)
  • 了解

    • 只能在序列化中使用
    • 字段名必须是外键字段
    • 相对于自定义序列化外键字段,自定义序列化字段是不能参与反序列化的,而子序列化必须为外键名,所以就无法入库
    • 在外键关联数据是多余时,需要明确many=True
    • 子序列化是单向操作,因为作为子序列的类必须写在上方,所以不能产生逆方向的子序列化
  • 实例

    from rest_framework import serializers
    
    from . import models
    
    class ModelSerializerStudent(serializers.ModelSerializer):
        phone = serializers.StringRelatedField(source="si_id.s_phone")# 正向,根据属性名查询
    
        class Meta:
            model=models.Stu
            fields="__all__"
    
    class ModelSerializerCourse(serializers.ModelSerializer):
        course_student = ModelSerializerStudent(many=True,read_only=True)
        class Meta:
            model = models.Course
            fields = "__all__"
    

view视图

APIView

APIView是REST framework提供的所有视图的基类,继承自Django的View父类。

APIView与View不同之处在于:

  • 传入到视图方法中的是REST framework的Request对象,而不是Django的HttpRequeset对象;

  • 视图方法可以返回REST framework的Response对象,视图会为响应数据设置(render)符合前端要求的格式;

  • 任何APIException异常都会被捕获到,并且处理成合适的响应信息;

  • 在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制。

  • 支持定义的属性:

    • authentication_classes 列表或元祖,身份认证类
    • permissoin_classes 列表或元祖,权限检查类
    • throttle_classes 列表或元祖,流量控制类
  • 在APIView中仍以常规的类视图定义方法来实现get() 、post() 或者其他请求方式的方法。

用户认证
  • drf自带的TokenAuthentication

    • 配置rest_framework.authtoken

    settings.py中INSTALLED_APPS中添加应用'rest_framework.authtoken'

    INSTALLED_APPS = [
      'django.contrib.admin',
      'django.contrib.auth',
      'django.contrib.contenttypes',
      'django.contrib.sessions',
      'django.contrib.messages',
      'django.contrib.staticfiles',
      'rest_framework',
      'api',
      'v02',
      'rest_framework.authtoken',
    ]
    
    • 数据迁移生成token表
    python manage.py migrate
    

    注意,注意一定不要执行``python manage.py makemigrations`

    image
    • 配置token认证类

      settings.py

      REST_FRAMEWORK = {
        'DEFAULT_RENDERER_CLASSES': ( # 默认响应渲染类
        'rest_framework.renderers.JSONRenderer', # json渲染器
        'rest_framework.renderers.BrowsableAPIRenderer',) ,# 浏览API渲染器 )
        # 自定义异常函数
        'EXCEPTION_HANDLER':'utils.custom_exception.exception_handler',
        # 认证类配置
        'DEFAULT_AUTHENTICATION_CLASSES':[
            'rest_framework.authentication.TokenAuthentication'
        ],
      }
      
      
    • 定义登录和注册方法
      views.py

      from django.contrib.auth import authenticate
      from django.contrib.auth.models import User
      
      # Create your views here.
      from rest_framework.authtoken.models import Token
      from rest_framework.response import Response
      from rest_framework.views import APIView
      
      class Signup(APIView):
          authentication_classes = [] # 去掉用户认证
          permission_classes = [] # 去掉权限认证
      
          def post(self,request):
              data = request.data
              user = User.objects.create_user(**data) # 创建用户
              user.save()
              token, created = Token.objects.update_or_create(user=user) # 创建token
              ret = {}
              ret["code"] = '0000'
              ret["msg"] = '注册成功'
              ret['data'] = {
                  "id":user.id,
                  "username":user.username,
                  "password": user.password,
                  "token":token.key
                             }
              return Response(ret)
      
      class Login(APIView):
          authentication_classes = [] # 去掉用户认证
          permission_classes = [] # 去掉权限认证
      
          def post(self,request):
              data = request.data
              res = authenticate(**data) # 验证用户名和密码
              ret = {}
              if res:
                  token,created = Token.objects.update_or_create(user=res) # 创建token
                  print(token)
                  ret["code"] = '0000'
                  ret["msg"] = '登录成功'
                  ret['data'] = token.key
              else:
                  ret["code"] = '9999'
                  ret["msg"] = '登录失败'
                  ret['data'] = None
              return Response(ret)
       
       # 用于测试
      class TestLogin(APIView):
      
          def get(self,request):
              return Response("{}".format(request.user))
      

      没带token v02/test/接口:

      image

      注册接口获得token


      image

      数据库token表存入token


      image

      使用该token访问v02/test/接口:

      image
  • 自定义身份认证类

    utils目录下custom_authentication.py

    # 0 游客
    # 1 普通会员
    # ...
    from django.contrib.auth.models import User
    from rest_framework.authentication import BaseAuthentication
    
    class MyAuthentication(BaseAuthentication):
        """
        一般重新定义用户模型和token管理方案,这个类必须重写
        """
    
        def authenticate(self, request):
            # 获取请求头中的token,如果传Authorization,就使用HTTP_Authorization全大写
    
            auth = request.META.get('HTTP_TOKEN', None)
    
            # 如果auth为空,表示未登录
            if not auth:
                # return None, None   返回一个user和token为空
                return 0,None # 0代表游客,可以自定义
    
            # 对token进行切割,如果不是2段,且第一段为token,同样表示未登录
            auth_list = auth.split()
            if not (len(auth_list) == 2 and auth_list[0] == 'token'):
                return None, None
    
            key = auth_list[1]
            try:
                user = User.objects.select_releted("auth_token").get(auth_token_key=key)
            except:
                return None, None
            return user, auth
    
        # 不用写内容,但是不写该函数会报错
        def authenticate_header(self, request):
    
            pass
    
    
权限控制
  • drf默认提供了一些权限类

    • rest_framework.permissions.AllowAny:游客和登录用户有全权限
    • rest_framework.permissions.IsAuthenticated:只有登录用户有全权限
    • rest_framework.permissions.IsAdminUser:只有后台用户(admin用户)有全权限
    • rest_framework.permissions.IsAuthenticatedOrReadOnly:游客有读权限,登录用户有全权限
  • 配置权限类

    • 全局配置

      settings.py

      注意:要去掉自定义认证,使用drf默认的认证配置

      REST_FRAMEWORK = {
          'DEFAULT_RENDERER_CLASSES': (  # 默认响应渲染类
              'rest_framework.renderers.JSONRenderer',  # json渲染器
          'rest_framework.renderers.BrowsableAPIRenderer',),  # 浏览API渲染器        )
          # 自定义异常函数
          'EXCEPTION_HANDLER': 'utils.custom_exception.exception_handler',
          # 认证类配置
          'DEFAULT_AUTHENTICATION_CLASSES': [
              'rest_framework.authentication.TokenAuthentication'
          ],
          # 权限类配置
          'DEFAULT_PERMISSION_CLASSES': [
              'rest_framework.permissions.IsAuthenticated',  # 配置为:只有登录用户有全权限
          ],
      }
      
    • 局部配置

      views.py

      class TestLogin(APIView):
        permission_classes = [AllowAny]  # 局部配置权限认证,表示游客和登录用户有全权限
      
        def get(self, request):
            return Response("{}".format(request.user))
      

      注释掉局部配置、注释掉请求头的token

      image

      有局部配置,没有token,表示没有token是游客也能访问到

      image
  • 自定义权限类

    需要自定义两个方法:

    def has_permission(self, request, view):
    
    def has_object_permission(self, request, view, obj):
    
    # 导入权限基类
    from rest_framework.permissions import BasePermission
    
    class LoginPermission(BasePermission):
    
      def has_permission(self, request, view):
    
          user = request.user
          # 在自定义身份认证中将0定义为游客身份
          if user == 0:
              return False
          else:
              return True
              
      # 可以通过定义不同的方法,给不同权限的人身份认证,
      def vip_permission(self, request, view):
    
          user = request.user
          # 在自定义身份认证中将0定义为游客身份
          if user >= 1:
              return False
          else:
              return True
             
      # obj为自定义身份认证中的user,一般使用django原生的用户认证才使用
      # def has_object_permission(self, request, view, obj):
      #     obj.user = request.user
      #     pass
    
    

    views.py

    class TestLogin(APIView):
        # 权限认证和身份认证都该为自定义配置
      permission_classes = [LoginPermission] 
      authentication_classes = [MyAuthentication]
      def get(self, request):
          return Response("{}".format(request.user))
    
流量控制

节流又叫限流,限制访问。就是通常一个用户在多次请求一个页面,或者点击一个链接的时候,前几次点击是没有问题的,但是一旦连续几次之后,就会出现访问受限,离下一次访问还有50秒等的字样,在django rest framework 中有一个专门的组件来做限制访问。 例如:手机接收验证码

  • drf默认频率类

    • 可选频率类

      • rest_framework.throttling.AnonRateThrottle : 限制所有匿名未认证用户,使用IP区分用户
      • rest_framework.throttling.UserRateThrottle : 限制认证用户,使用User id 来区分。
      • rest_framework.throttling.ScopedRateThrottle : 限制用户对于每个视图的访问频次,使用ip或user id
    • 配置频率类(对于上述用户认证的频率配置)

      • 全局配置

        REST_FRAMEWORK = {
        'DEFAULT_RENDERER_CLASSES': (  # 默认响应渲染类
            'rest_framework.renderers.JSONRenderer',  # json渲染器
            'rest_framework.renderers.BrowsableAPIRenderer',),  # 浏览API渲染器 )
        # 自定义异常函数
        'EXCEPTION_HANDLER': 'utils.custom_exception.exception_handler',
        # 认证类配置
        'DEFAULT_AUTHENTICATION_CLASSES': [
            'rest_framework.authentication.TokenAuthentication'
        ],
        # 权限类配置
        'DEFAULT_PERMISSION_CLASSES': [
            'rest_framework.permissions.IsAuthenticated',  # 只有登录用户有全权限
        ],
        "DEFAULT_THROTTLE_CLASSES":["rest_framework.throttling.AnonRateThrottle",'rest_framework.throttling.UserRateThrottle'], # 全局配置频率类
        "DEFAULT_THROTTLE_RATES":{
            "anon":'5/m', # 对应的key设置访问频率
            "user":'10/m',  # 对应的key设置访问频率
            }
        }
        
        

        views.py

        class TestLogin(APIView):
          permission_classes = [AllowAny]
          def get(self, request):
              return Response("{}".format(request.user))
        
    未登录用户访问5次后,效果:
    
    ![image](https://upload-images.jianshu.io/upload_images/12041448-8edb4cf73c26df69.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    已登录用户访问10次,效果:
    
    ![image](https://upload-images.jianshu.io/upload_images/12041448-9b98d101416d3277.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
  - 局部配置(在视图中进行配置)
    
  ```py
  # 导入自定义频率
  from rest_framework.throttling import AnonRateThrottle
  from rest_framework.throttling import UserRateThrottl
  
  class TestLogin(APIView):
    throttle_classes =[AnonRateThrottle,UserRateThrottle]
    
    def get(self, request):
        return Response("{}".format(request.user))
  ```
  • 使用ScopedRateThrottle来自定义频率

    替换原来的settings.py频率配置部分

      REST_FRAMEWORK = {
        "DEFAULT_THROTTLE_CLASSES":["rest_framework.throttling.ScopedRateThrottle"], # 全局配置频率类
        "DEFAULT_THROTTLE_RATES":{
            'five':'5/m', # 自定义频率字段
            'eight':'8/m'
        }
    }
    

    视图中使用throttle_scope来使用自定义频率

    class TestLogin(APIView):
      permission_classes = [AllowAny]
      throttle_scope='eight' # 使用throttle_scope来使用自定义频率
      def get(self, request):
          return Response("{}".format(request.user))
    
  • 自定义频率类(需要再看吧,跳过)
请求和响应
  • Request类

    对一个APIView的子类, 重写get, post, put等方法就相当于解析这个路径的get, post, put请求,

    请求对象就是request对象, http header body 的内容都被包含在request对象中.
    request对象的类来自 from rest_framework.request import Request

    判断对象是否是某个类实例化而来

    1. from rest_framework.request import Request

    2. if isinstance(request, Request)

    下面分别分析不同情况的参数位置和类型, 最终写出一个方法能够将任何类型的请求参数统一转换为dict方便之后的逻辑编写。

    • GET

      get请求中参数都会以http://xxx.com/api/getjson?param1=asdf&m2=123这样的形式拼接在url后面。

      在request对象中

      • request.query_params 中可以获取?param1=32¶m2=23形式的参数.

      • request.query_params 返回的数据类型为QueryDict

      • QueryDict转为普通python字典. query_params.dict()即可。

    • POST

      post请求参数都在请求体中, 但是其实你的url可以写成get的形式, 最终结果, 参数会有两部分组成, 一部分在url中, 一部分在http body 中, 但是非常不建议这样做。

      接下来的代码编写也不会考虑这样的情况, post 仅考虑所有参数都在http body 中的情况。


      image
  • PUT

    image
  • PATCH

    ![image](https://upload-images.jianshu.io/upload_images/12041448-8e942ba2c2f4b5c3.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
  • DELETE

    image
  • 编写参数统一处理的方法

from django.http import QueryDict
from rest_framework.request import Request

def get_parameter_dic(request, *args, **kwargs):
    if isinstance(request, Request) == False:
        return {}

    query_params = request.query_params
    if isinstance(query_params, QueryDict):
        query_params = query_params.dict()
    result_data = request.data
    if isinstance(result_data, QueryDict):
        result_data = result_data.dict()

    if query_params != {}:
        return query_params
    else:
        return result_data
  • Response类

    REST framework提供了一个响应类Response,使用该类构造响应对象时,响应的具体数据内容会被转换(render渲染)成符合前端需求的类型。

    REST framework提供了Renderer 渲染器,用来根据请求头中的Accept(接收数据类型声明)来自动转换响应数据到对应格式。如果前端请求中未进行Accept声明,则会采用默认方式处理响应数据,我们可以通过配置来修改默认响应格式。

    在settings.py中加入:

    REST_FRAMEWORK = { 
      'DEFAULT_RENDERER_CLASSES': ( # 默认响应渲染类
      'rest_framework.renderers.JSONRenderer', # json渲染器
      'rest_framework.renderers.BrowsableAPIRenderer',) # 浏览API渲染器 )
    }
    
    • 构造方式

    Response(data, status=None, template_name=None, headers=None, content_type=None)

    • data: 为响应准备的序列化处理后的数据;

    • status: 状态码,默认200;

    • template_name: 模板名称,如果使用HTMLRenderer 时需指明;

    • headers: 用于存放响应头信息的字典;

    • content_type: 响应数据的Content-Type,通常此参数无需传递,REST framework会根据前端所需类型数据来设置该参数。

      • 1).data :传给response对象的序列化后,但尚未render处理的数据

      • 2).status_code:状态码的数字

      • 3).content:经过render处理后的响应数据,django在报错的时候,会直接在浏览器暴露报错信息,所以我们可以通过重写异常处理函数来处理服务器的报错信息,重写Response类格式化响应输出信息

    • 重写Response类

utils文件夹下新增custom_response.py

```py
from rest_framework.response import Response
# 自定义一个响应,继承自Response类
class CustomResponse(Response):
    def __init__(self, *args, code='0000', msg="成功", **kwargs):
        # 格式化data
        data = {
            "code": code,
            "message": msg
        }
        if args is not None:
            data["data"] = args[0]
            kwargs["data"] = data
        elif "data" in kwargs:
            data["data"] = kwargs["data"]
            kwargs["data"] = data

        super().__init__(**kwargs)

```

views.py下修改:

 ![image](https://upload-images.jianshu.io/upload_images/12041448-7b888e74144989c9.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

效果如下:

![image](https://upload-images.jianshu.io/upload_images/12041448-75c6adc034fa1b39.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  • 重写异常类

    utils文件夹下新增custom_exception.py

    from rest_framework import status
    from rest_framework.exceptions import ValidationError
    from rest_framework.views import exception_handler as drf_exception_handler
    
    from utils.custom_response import CustomResponse
    
    def exception_handler(exc, context):
        """
        自定义异常处理
        :param exc: 别的地方抛的异常就会传给exc
        :param context: 字典形式。抛出异常的上下文(即抛出异常的出处;即抛出异常的视图)
        :return: Response响应对象
        """
        response = drf_exception_handler(exc, context)
        if response is None:
            # drf 处理不了的异常
            print('%s - %s - %s' % (context['view'], context['request'].method, exc))
            return CustomResponse({'detail': '服务器错误'}, code=500, msg="服务器内部错误",
                                   status=status.HTTP_500_INTERNAL_SERVER_ERROR, exception=True)
        if isinstance(exc, ValidationError):
            message = ""
            data = response.data
            for key in data:
                message += ";".join(data[key])
            return CustomResponse(None, code="9999", msg=message)
        return response
    

    settings.py新增指定异常处理函数

    REST_FRAMEWORK = {
      'DEFAULT_RENDERER_CLASSES': ( # 默认响应渲染类
      'rest_framework.renderers.JSONRenderer', # json渲染器
      'rest_framework.renderers.BrowsableAPIRenderer',) ,# 浏览API渲染器 )
      # 自定义异常函数
      'EXCEPTION_HANDLER':'utils.custom_exception.exception_handler',
    }
    

    效果:


    image
GenericAPIView

rest_framework.generics.GenericAPIView,继承自APIVIew,增加了对于列表视图和详情视图可能用到的通用支持方法。通常使用时,可搭配一个或多个Mixin扩展类。

基本使用
  • 支持定义的属性:

    • 列表视图与详情视图通用:-必须定义

      • queryset 列表视图的查询集
      • serializer_class 视图使用的序列化器
    • 列表视图使用:

      • pagination_class 分页控制类
      • filter_backends 过滤控制后端
    • 详情页视图使用:

      • lookup_field 查询单一数据库对象时使用的条件字段,默认为'pk'
      • lookup_url_kwarg 查询单一数据时URL中的参数关键字名称,默认与look_field相同
  • 提供的方法:

    • 列表视图与详情视图通用

      • get_queryset(self)

      返回视图使用的查询集,是列表视图与详情视图获取数据的基础,默认返回 queryset属性,可以重写

      • get_serializer_class(self)

      返回序列化器类,默认返回 serializer_class,可以重写

      • get_serializer(self, args, *kwargs)

        常用,返回序列化器对象,被其他视图或扩展类使用,如果我们在视图中想要获取序列化器对象,可以直接调用此方法。

      • 实例:

        注释掉setiings.py中的认证配置和权限配置,以便我们后面讲解方便。
        注释掉之前为了讲解字段验证新增的startTime、endTime代码。

        修改原来的Projects

        from rest_framework.generics import GenericAPIView
        # 两大属性
        # 查询集
        queryset = Project.objects.all()
        # 序列化器
        serializer_class = serializers.ModelSerializerProject
        
        # 序列化器的使用
        def get(self, request):
            # 获取查询集,等同于projects = Project.objects.all()
            projects =self.get_queryset()
            # 进行序列化,等同于serializer = serializers.ModelSerializerProject(instance=projects, many=True)
            serializer = self.get_serializer(instance=projects,many=True)
            return Response(serializer.data)
        
        image
  • 使用django-filter实现过滤查询

    • 安装django-filter pip install django-filter

    • 添加至INSTALLED_APPS

      INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'rest_framework',
        'api',
        'v02',
        'rest_framework.authtoken',
        'django_filters',
      ]
      
    • 设置过滤后端

    对单个GenericAPIView视图设置过滤器后端

    class Projects(GenericAPIView):
    
       queryset = Project.objects.all()
       serializer_class = serializers.ModelSerializerProject
       # 针对某个GenericAPIView视图添加过滤
       filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
       
       def get(self, request):
          
           projects = self.get_queryset()
           serializer = self.get_serializer(instance=projects, many=True)
           return Response(serializer.data)
    
    

    我们还可以在settings.py中添加全局设置默认过滤器后端,可以通过、DEFAULT_FILTER_BACKENDS设置通用的过滤后端

    # rest_framework框架全局设置
    REST_FRAMEWORK = {
       "DEFAULT_FILTER_BACKENDS":[# 设置全局引擎
           'django_filters.rest_framework.DjangoFilterBackend', # 设置后端过滤
       ]
    }
    
    
    • 基本使用
    # 使用GenericAPIView
     class Projects(GenericAPIView):
         # 两大属性
         # 查询集
         queryset = Project.objects.all()
         # 序列化器
         serializer_class = serializers.ModelSerializerProject
         # 针对某个GenericAPIView视图添加过滤
         filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
         filterset_fields=['name','type']
         # 序列化器的使用
         def get(self, request):
             projects = self.get_queryset()
             # 使用filter_queryset方法对查询集进行过滤
             serializer = self.get_serializer(instance=self.filter_queryset(projects), many=True)
             return Response(serializer.data)
    

    过滤出name=hello测开的数据:

    image
  • 重写FilterSet类

    我们会发现,查询参数的名字太长了并且也不支持模糊查询,我们可以通过重写FilterSet类来实现自定义查询参数的字段名和查询规则。

    • 在想自定义查询类的应用下新建custom_filters.py,不是放在项目文件utils下,因为不是公共的,每个应用都不同的查询类
```py
from django_filters import rest_framework as filters
from . import models

class ProjectFilter(filters.FilterSet):
    # field_name要过滤的字段 lookup_expr要过滤的规则
    name = filters.CharFilter(field_name="name", lookup_expr='contains')  # 对名字进行模糊查询
    ver = filters.CharFilter(field_name="version")  # 字段可以自己重新命名,对version进行精确查询

    class Meta:
        model = models.Project
        fields = ['name','ver']  # 指定查询参数
```

- 自定义过滤字段和模型对照表

![image](https://upload-images.jianshu.io/upload_images/12041448-45475a648e36ad55.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

- 视图中指定查询类

```py
# 使用GenericAPIView
class Projects(GenericAPIView):
    queryset = Project.objects.all() 
    serializer_class = serializers.ModelSerializerProject
    # 针对某个GenericAPIView视图添加过滤
    filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
     # 指定查询类
    filterset_class = ProjectFilter

    def get(self, request):
        projects = self.get_queryset()
        # 使用filter_queryset方法对查询集进行过滤
        serializer = self.get_serializer(instance=self.filter_queryset(projects), many=True)
        return Response(serializer.data)
```

![image](https://upload-images.jianshu.io/upload_images/12041448-04850db0ec269c3f.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  • 排序

    • 设置排序的backends

    全局配置,settings.py

    # rest_framework框架全局设置
    REST_FRAMEWORK = {
        "DEFAULT_FILTER_BACKENDS":[# 设置全局引擎
            'rest_framework.filters.OrderingFilter', # 排序引擎
    
        ]
        }
    

    单个视图中设置

    from rest_framework import filters
    
    class Projects(GenericAPIView):
      queryset = Project.objects.all()
      serializer_class = serializers.ModelSerializerProject
      # 针对某个GenericAPIView视图添加过滤,filters.OrderingFilter视图中设置排序引擎
      filter_backends = [django_filters.rest_framework.DjangoFilterBackend,filters.OrderingFilter]
      filterset_class = ProjectFilter
    
      def get(self, request):
          projects = self.get_queryset()
          serializer = self.get_serializer(instance=self.filter_queryset(projects), many=True)
          return Response(serializer.data)
    
    • 指定排序字段

    使用ordering_fields属性显示指定要排序的字段,不指定则默认序列化器上的任何可读字段都可用来排序

    class Projects(GenericAPIView):
       queryset = Project.objects.all()
       serializer_class = serializers.ModelSerializerProject
       # 针对某个GenericAPIView视图添加过滤,filters.OrderingFilter视图中设置排序引擎
       filter_backends = [django_filters.rest_framework.DjangoFilterBackend,filters.OrderingFilter]
       filterset_class = ProjectFilter
       # 指定排序字段
       ordering_fields = ['id']
       
       def get(self, request):
           projects = self.get_queryset()
           serializer = self.get_serializer(instance=self.filter_queryset(projects), many=True)
           return Response(serializer.data)
    

    需要使用ordering=idordering=-id字段来指定排序规则

    image
    • 指定排序默认字段

    使用ordering属性指定默认排序字段

    class Projects(GenericAPIView):
      queryset = Project.objects.all()
      serializer_class = serializers.ModelSerializerProject
      # 针对某个GenericAPIView视图添加过滤,filters.OrderingFilter视图中设置排序引擎
      filter_backends = [django_filters.rest_framework.DjangoFilterBackend,filters.OrderingFilter]
      filterset_class = ProjectFilter
      # 指定排序字段
      ordering_fields = ['id']
      # 指定默认排序字段,倒序
      ordering = ['-id']
      
      def get(self, request):
          projects = self.get_queryset()
          serializer = self.get_serializer(instance=self.filter_queryset(projects), many=True)
          return Response(serializer.data)
    

    此时,不用在url中指定ordering,就可以默认按id倒序排列

    image
  • 分页

我们数据库有几千万条数据,这些数据需要展示,我们不可能直接从数据库把数据全部读取出来.
因为这样会给内存造成巨大的压力,很容易就会内存溢出,所以我们希望一点一点的取。同样,展示的时候也是一样的,我们必定会对数据进行分页显示。

  • 普通分页 查第n页,每页显示n条数据

1)分页器配置文件

应用下新建custom_paginations.py

from rest_framework import pagination

class PageNumberPagination(pagination.PageNumberPagination):
    """查第n页,每页显示n条数据"""
    page_size = 1  # 指定每页默认显示多少条数据
    page_size_query_param = 'size'  # URL参数中每页显示条数的参数
    page_query_param = 'page'  # URL中页码的参数
    max_page_size = 40  # 每页最多显示多少条数据

2)视图函数使用分页器

class Projects(GenericAPIView):
  queryset = Project.objects.all()
  serializer_class = serializers.ModelSerializerProject
  filter_backends = [django_filters.rest_framework.DjangoFilterBackend,filters.OrderingFilter]
  filterset_class = ProjectFilter
  ordering_fields = ['id']
  # 指定默认排序字段
  ordering = ['-id']
  # 1. 指定分页器对象
  pagination_class = PageNumberPagination

  def get(self, request):
      # 在过滤后端下,这些写
      projects = self.filter_queryset(self.get_queryset())
      # 2. 使用自己配置的分页器调用分页方法进行分页
      page_obj = self.paginate_queryset(projects)
      # 3. 序列化我们分页好的数据
      serializer = self.get_serializer(instance=page_obj, many=True)
      # 4. 返回带上一页/下一页连接的页面
      return self.get_paginated_response(serializer.data)

效果:可以在过滤下分页,选择第几页,每页展示多少条数据

image

前端展示分页时,可以直接调用上一页链接和下一页链接

image
  • 偏离分页 在第n个位置,向后查n条数据 (示例代码,没跑过,需要时,抄代码)

    1)分页器配置文件

    from rest_framework import pagination
    
    class LimitOffsetPagination(pagination.LimitOffsetPagination):
      """在第n个位置,向后查n条数据"""
      default_limit = 1 # 指定默认查多少条数据
      limit_query_param = 'limit' # URL中指定查多少条数据的参数
      offset_query_param = 'offset' # URL中指定从第几条数据开始查的参数
      max_limit = 999 # 最大显示多少条数据
    

    2)视图函数使用分页器

    from v03 import pagination
    
    class Students(GenericAPIView,ListModelMixin):
        queryset = Stu.objects.all()
        serializer_class = ModelSerializerStudent
        pagination_class = pagination.LimitOffsetPagination # 1. 指定分页器对象
        def get(self,request,*args,**kwargs):
            students = self.get_queryset()
            # 2. 使用自己配置的分页器调用分页方法进行分页
            page_obj = self.paginate_queryset(students)
            # 3.序列化我们分页好的数据
            serializer = self.get_serializer(instance=page_obj,many=True)
            # 4. 返回数据
            # return Response(serializer.data)
            # 4. 返回带上一页/下一页连接的页面
            return self.get_paginated_response(serializer.data)
    
  • 加密分页 (示例代码,没跑过,需要时,抄代码)

    1)分页器配置文件

    class CursorPagination(pagination.CursorPagination):
      """加密游标的分页"""
      cursor_query_param = 'cursor'  # 游标(这是加密的游标)
      # ordering = '-id' # 从后往前取数据
      ordering = 'id' # 从前往后取数据
      page_size = 1  # 每页显示的条数
    

    2)视图函数使用分页器

    from v03 import pagination
    
    class Students(GenericAPIView,ListModelMixin):
        queryset = Stu.objects.all()
        serializer_class = ModelSerializerStudent
        pagination_class = pagination.CursorPagination # 1. 指定分页器对象
        def get(self,request,*args,**kwargs):
            students = self.get_queryset()
            # 2. 使用自己配置的分页器调用分页方法进行分页
            page_obj = self.paginate_queryset(students)
            # 3.序列化我们分页好的数据
            serializer = self.get_serializer(instance=page_obj,many=True)
            # 4. 返回数据
            # return Response(serializer.data)
            # 4. 返回带上一页/下一页连接的页面
            return self.get_paginated_response(serializer.data)
    
View、APIView、GenericAPIView的区别

https://www.cnblogs.com/hanbowen/p/9885358.html

mixins类

和GenericAPIView一同搭配使用的还有五个扩展类,他们分别是ListModelMixin, CreateModelMixin,UpdataModelMixin,RetrieveModelMixin,DestroyModelMixin

image

mixin 混合类,不能单独使用,需要利用python支持多继承结合GenericAPIView一起用

class Projects(ListModelMixin,GenericAPIView):
    queryset = Project.objects.all()
    serializer_class = serializers.ModelSerializerProject
    filter_backends = [django_filters.rest_framework.DjangoFilterBackend, filters.OrderingFilter]
    filterset_class = ProjectFilter
    ordering_fields = ['id']
    ordering = ['-id']

    def get(self, request,*args,**kwargs):
        return self.list(request,*args,**kwargs)
  • ListModelMixin(查询所有)

    提供了list(request,*args,**kwargs)方法快速实现列表视图,返回的是状态码是默认的200

  • CreateModelMixin(创建对象)

    创建视图的扩展类,提供类create(request,*args,**kwargs)方法快速实现创建资源的视图,成功后返回201状态码

    如果序列化器对前端发送的数据验证失败,则返回400的错误

  • UpdataModelMixin(更新视图)

    更新视图扩展类,提供update(request, *args, **kwargs)方法,可以快速实现更新一个存在的数据对象。

    也同时提供partial_update(request, *args, **kwargs)方法,可以实现局部更新。

    成功返回200,序列化器校验数据失败时,返回400错误。

  • RetrieveModelMixin(查询单个对象详情)

    详情视图扩展类(即查询单个对象),提供了类retrieve方法

    可以快速实现返回一个存在的数据对象

    如果存在,返回200,否则就返回404

  • DestroyModelMixin(删除视图)

    删除视图扩展类,提供destroy(request,*args,**kwargs)方法,可以快速实现删除一个存在的数据对象

GenericAPIView+mixins

继承这些类时,要重写下面的方法。

image
image
image
image
image
ViewSet的使用
  • 使用视图集ViewSet,可以将一系列逻辑相关的动作放到一个类中:

    • list() 提供一组数据

    • retrieve() 提供单个数据

    • create() 创建数据

    • update() 保存数据

    • destory() 删除数据

      ViewSet视图集类不再实现get()、post()等方法,而是实现动作 action 如 list() 、create() 等。

  • action属性

    在视图集中,我们可以通过action对象属性来获取当前请求视图集时的action动作是哪个

  • 常用视图集父类

    1) ViewSet

    继承自APIView,作用也与APIView基本类似,提供了身份认证、权限校验、流量管理等。在ViewSet中,没有提供任何动作action方法,需要我们自己实现action方法。

    2)GenericViewSet

    继承自GenericAPIView,作用也与GenericAPIVIew类似,提供了get_object、get_queryset等方法便于列表视图与详情信息视图的开发。

    3)ModelViewSet

    继承自GenericAPIVIew,同时包括了ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin。

    4)ReadOnlyModelViewSet

    继承自GenericAPIVIew,同时包括了ListModelMixin、RetrieveModelMixin。

  • GenericViewSet例子

    views.py

    # 使用GenericViewSet
    class Projects(ListModelMixin,GenericViewSet):
        queryset = Project.objects.all()
        serializer_class = serializers.ModelSerializerProject
        filter_backends = [django_filters.rest_framework.DjangoFilterBackend, filters.OrderingFilter]
        filterset_class = ProjectFilter
        ordering_fields = ['id']
        ordering = ['-id']
    

    路由配置 urls.py,

    # 路由配置中显示指定请求方法对应的视图
    urlpatterns = [
      path('projects/',views.Projects.as_view({'get': 'list'}))
     ]
    
  • ModelViewSet

ModelViewSet继承了下图这些类,所以我们在使用ModelViewSet时,不用再加继承ListModelMixin等。

image

views.py

# 使用ModelViewSet
 class Projects(ModelViewSet):
     queryset = Project.objects.all()
     serializer_class = serializers.ModelSerializerProject
     filter_backends = [django_filters.rest_framework.DjangoFilterBackend, filters.OrderingFilter]
     filterset_class = ProjectFilter
     ordering_fields = ['id']
     ordering = ['-id']
 # 路由配置中显示指定请求方法对应的视图
 urlpatterns = [
   path('projects/',views.Projects.as_view({'get': 'list','post':'create'}))
  ]
image

路由控制

  • 自定义路由(原始方式) (用到再看)

  • 半自动路由(视图类集成ModelViewSet)(用到再看)

  • 全自动路由(自动生成路由)

    urls.py

    router = routers.DefaultRouter() # 获取一个routers实例
    router.register("projects", views.Projects)# 注册路由信息,两个参数:一个是匹配路由,一个是视图类
    urlpatterns = [
    ]
    urlpatterns += router.urls
    
    image

view视图总结

  • DRF中所有的视图文件

    from rest_framework import views       # APIView
    from rest_framework import generics    # 公共通用视图类:GenericAPIView,及各种组合视图类CreateAPIView、ListAPIView、RetrieveAPIView等
    from rest_framework import mixins      # 混合继承类:CreateModelMixin、ListModelMixin、RetrieveModelMixin、UpdateModelMixin、DestroyModelMixin
    from rest_framework import viewsets    # 重写as_view: ViewSetMixin;其他类都是帮助去继承ViewSetMixin
    
  • DRF中的视图图谱

    首先 django是继承 view的,DRF是从APIView开始继承起,APIView封装了request,其中包含了data、query_params等属性、方法。

    然后 GenericAPIView封装了 get_queryset() 和 get_serializer();ViewSetMixin重写了 as_view()方法。

    随后 GenericViewSet帮忙继承GenericAPIView和ViewSetMixin。

    最后最高层的封装是 ModelViewSet。

    image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349