Django Rest Framework组件

Django框架 / 2021-05-14

这篇属于Django Rest Framework系列,但我忘了发表,现在补回来。

一、过滤filtering

RESTful API规范之一是要有数据筛选功能,即只输出指定的数据,这需要后端提供过滤功能,我们可以通过使用第三方模块django-filter来达到目的。

1.1、filter安装配置

1.1.1、安装
pip3 install django-filter  # 需要在settings的APP中注册
1.1.2、注册过滤器
INSTALLED_APPS = [  # 注册
    'django_filters'
]


REST_FRAMEWORK = {  # 启用
    'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}

1.2、使用filter

涉及到models、views、序列化器等。

1.2.1、models代码

写好代码后迁移以创建表结构

from django.db import models

# Create your models here.


class Authors(models.Model):
    name = models.CharField(max_length=16, verbose_name='作者')
    gender = models.IntegerField(choices=((1, 'Male'), (2, 'Female')), verbose_name='性别')

    def __str__(self):
        return self.name

    class Meta(object):
        verbose_name_plural = '作者'


class Books(models.Model):

    name = models.CharField(max_length=16, verbose_name='书名')
    price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name='价格')
    # 为什么用DO_NOTHING?如果用CASCADE,一旦删除数据,关联的所有数据也会被一并删除。
    author = models.ForeignKey(to=Authors, on_delete=models.DO_NOTHING)

    def __str__(self):
        return self.name

    class Meta(object):
        verbose_name_plural = '书籍'
1.2.2、序列化器代码
from rest_framework import serializers
from just_test import models


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Books
        fields = '__all__'
    # 因为Book中author是外键字段,GET请求到的是ID号,我们要把它转成实际对应的名称提高用户体验。
    author = serializers.CharField(source='author.name')
    extra_kwargs = {
        'author': 'write_only',  # 把GET到的author的ID号隐藏掉,只剩下上面定义的author。
    }
1.2.3、views代码
from rest_framework.generics import ListAPIView
from just_test.models import Books
from utils.myserializer import BookSerializer


# Create your views here.


class BookView(ListAPIView):
    queryset = Books.objects.all()
    serializer_class = BookSerializer
    filter_fields = ('price',)
1.2.4、urls代码
from django.contrib import admin
from django.urls import path
from just_test import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/books/', views.BookView.as_view())
]
1.3、验证效果
未过滤

postman访问:127.0.0.1:8000/api/books/

[
    {
        "id": 1,
        "author": "三溪",
        "name": "津门除妖记",
        "price": "10.00"
    },
    {
        "id": 2,
        "author": "三溪",
        "name": "今村异闻录",
        "price": "8.00"
    },
    {
        "id": 3,
        "author": "刘慈欣",
        "name": "三体",
        "price": "21.00"
    },
    {
        "id": 4,
        "author": "刘慈欣",
        "name": "流浪地球",
        "price": "16.00"
    }
]
过滤

postman访问:127.0.0.1:8000/api/books/?price=21

[
    {
        "id": 3,
        "author": "刘慈欣",
        "name": "三体",
        "price": "21.00"
    }
]

二、排序

还是以上面的数据为基础做实验。

2.1、直接排序

rest_framework有自带的过滤模块OrderingFilter,但是OrderingFilter跟上面不一样,它是排序模块。请求时,rest_framework会检查url后面有没有带ordering参数,有则按照ordering指定字段进行排序。

2.1.1、views代码
from rest_framework.generics import ListAPIView
from just_test.models import Books
from utils.myserializer import BookSerializer
from rest_framework.filters import OrderingFilter  # 导入模块


# Create your views here.


class BookView(ListAPIView):
    queryset = Books.objects.all()
    serializer_class = BookSerializer
    filter_backends = [OrderingFilter]  # 启用OrderingFilter模块
    ordering_fields = ('price', 'id')  # 指定排序字段
2.1.2、验证效果

postman访问:127.0.0.1:8000/api/books/?ordering=-price,得到结果是按照价格由高到低进行排序。

[
    {
        "id": 3,
        "author": "刘慈欣",
        "name": "三体",
        "price": "21.00"
    },
    {
        "id": 4,
        "author": "刘慈欣",
        "name": "流浪地球",
        "price": "16.00"
    },
    {
        "id": 1,
        "author": "三溪",
        "name": "津门除妖记",
        "price": "10.00"
    },
    {
        "id": 2,
        "author": "三溪",
        "name": "今村异闻录",
        "price": "8.00"
    }
]

三、分页

当API查询请求数据量比较大的时候,一次性返回就不太现实也不合理,应该提供一种分页展示的方法供用户选择一页展示多少条。

3.1、全局分页

3.1.1、settings配置

全局分页是最简单的,因为rest_framework有内置模块,只需要在settings里面配置如下两行代码即可:

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS':  'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 3  # 每页数目
}
3.1.2、验证效果

postman访问:127.0.0.1:8000/api/books/

{
    "count": 4,
    "next": "http://127.0.0.1:8000/api/books/?page=2",
    "previous": null,
    "results": [
        {
            "id": 1,
            "author": "三溪",
            "name": "津门除妖记",
            "price": "10.00"
        },
        {
            "id": 2,
            "author": "三溪",
            "name": "今村异闻录",
            "price": "8.00"
        },
        {
            "id": 3,
            "author": "刘慈欣",
            "name": "三体",
            "price": "21.00"
        }
    ]
}

3.2、自定义分页类

自定义分页类用法也非常简单,几行代码即可搞掂!

3.2.1、自定义分页类
from rest_framework.pagination import PageNumberPagination  # 导入rest_framework分页类


class MyPageNumberPagination(PageNumberPagination):  # 自定义类必须继承该类
    page_size = 3  # 默认一页展示的数据条数
    page_query_param = 'page'  # 自定义分页关键字
    page_size_query_param = 'size'  # 每页展示条数,但不能超过max_page_size。
    max_page_size = 10  # 每页最大展示条数
3.2.2、views代码
from rest_framework.generics import ListAPIView
from just_test.models import Books
from utils.myserializer import BookSerializer
from utils.mypagination import MyPageNumberPagination  # 导入自定义的分页类

# Create your views here.


class BookView(ListAPIView):
    queryset = Books.objects.all()
    serializer_class = BookSerializer
    pagination_class = MyPageNumberPagination  # 启用自定义分页类
3.2.3、验证效果
默认分页

postman访问:127.0.0.1:8000/api/books/,此为使用后端默认配置,返回结果如下所示:

{
    "count": 7,
    "next": "http://127.0.0.1:8000/api/books/?page=2",
    "previous": null,
    "results": [
        {
            "id": 1,
            "author": "三溪",
            "name": "津门除妖记",
            "price": "10.00"
        },
        {
            "id": 2,
            "author": "三溪",
            "name": "今村异闻录",
            "price": "8.00"
        },
        {
            "id": 3,
            "author": "刘慈欣",
            "name": "三体",
            "price": "21.00"
        }
    ]
}
自定义分页

postman访问:127.0.0.1:8000/api/books/?page=2&size=3,此为每页展示三条,且只访问第二页的那三条:

{
    "count": 7,
    "next": "http://127.0.0.1:8000/api/books/?page=3&size=3",
    "previous": "http://127.0.0.1:8000/api/books/?size=3",
    "results": [
        {
            "id": 4,
            "author": "刘慈欣",
            "name": "流浪地球",
            "price": "16.00"
        },
        {
            "id": 5,
            "author": "金庸",
            "name": "笑傲江湖",
            "price": "16.00"
        },
        {
            "id": 6,
            "author": "金庸",
            "name": "越女剑",
            "price": "11.00"
        }
    ]
}

3.3、游标分页类

上面的自定义普通分页类可以指定访问第几页,假设要查询第1000页的数据,它是一页一页地查,直到第1000页再把那一页给你拿出来;就好比翻书,它是一页一页地翻到第1000页书再展示给你看,效率可想而知!

Cursor,游标,这个分页类比较特殊。特殊在它是按照字段先进行排序,排序后只能一页一页地翻,不能跳转,因为不需要像上面的普通分页一下子跳很多页,所以效率相比很高,但是用户得一页一页地点击查看。

它速度快,但缺点页很明显,用户无法控制,所有参数都在后台写死了的,因此仅适用于某些固定场景!

3.3.1、游标分页代码
from rest_framework.pagination import CursorPagination


class MyPageNumberPagination(CursorPagination):
    cursor_query_param = 'cursor'  # 分页关键字,前端只能看,无法改。
    page_size = 3  # 每页显示条数
    ordering = 'price'  # 排序的字段,固定死的。
3.3.2、views代码
from rest_framework.generics import ListAPIView
from just_test.models import Books
from utils.myserializer import BookSerializer
from utils.mypagination import MyPageNumberPagination

# Create your views here.


class BookView(ListAPIView):
    queryset = Books.objects.all()
    serializer_class = BookSerializer
    pagination_class = MyPageNumberPagination
3.3.3、验证效果

postman或浏览器访问:127.0.0.1:8000/api/books/,得到结果如下所示:

{  # 只能点击next跳到下一页,其它所有操作都不行。
    "next": "http://127.0.0.1:8000/api/books/?cursor=cD0xMS4wMA%3D%3D",
    "previous": null,
    "results": [
        {
            "id": 2,
            "author": "三溪",
            "name": "今村异闻录",
            "price": "8.00"
        },
        {
            "id": 1,
            "author": "三溪",
            "name": "津门除妖记",
            "price": "10.00"
        },
        {
            "id": 6,
            "author": "金庸",
            "name": "越女剑",
            "price": "11.00"
        }
    ]
}

四、异常处理

以往我们程序遇到故障时,会直接报错并把错误代码一并展示出来,但很多时候我们并不希望被用户看到这些错误信息和代码,所以我们需要自定义异常处理功能。

未完待续

REST framework为我们提供了异常处理模块exception_handler来自定义异常处理功能。

作用,统一错误信息的返回,记录日志

世间微尘里 独爱茶酒中