23

第 9 篇:实现分类、标签、归档日期接口

 4 years ago
source link: http://www.cnblogs.com/xueweihan/p/13095653.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

auaANju.jpg!web

作者: HelloGitHub-追梦人物

我们的博客有一个侧边栏功能,分别列出博客文章的分类列表、标签列表、归档时间列表,通过点击侧边栏对应的条目,还可以进入相应的页面。例如点击某个分类,博客将跳转到该分类下全部文章列表页面。这些数据的展示都需要开发对应的接口,以便前端调用获取数据。

分类列表、标签列表实现比较简单,我们这里给出接口的设计规范,大家可以使用前几篇教程中学到的知识点轻松实现(具体实现可参考 GtiHub 上的源代码 )。

分类列表接口: /categories/

标签列表接口:/tags/

归档日期列表的接口实现稍微复杂一点,因为我们需要从已有文章中归纳文章发表日期。事实上,我们在上一部教程 HelloDjango - Django博客教程(第二版)的 页面侧边栏:使用自定义模板标签 已经讲解了如何获取归档日期列表,只是当时返回的归档日期列表直接用于模板的渲染,而这里我们需要将归档日期列表序列化后通过 API 接口返回。

具体来说,获取博客文章发表时间归档列表的方法是调用查询集(QuerySet)的 dates 方法,提取记录中的日期。核心代码就一句:

Post.objects.dates('created_time', 'month', order='DESC')

这里 Post.objects.dates 方法会返回一个列表,列表中的元素为每一篇文章(Post)的创建日期(已去重),日期都是 Python 的 date 对象,精确到月份,降序排列。

有了返回的归档日期列表,接下来就实现相应的 API 接口视图函数:

blog/views.py

from rest_framework import mixins, status, viewsets
from rest_framework.decorators import action
from rest_framework.serializers import DateField

class PostViewSet(
    mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
	# ...

    @action(
        methods=["GET"], detail=False, url_path="archive/dates", url_name="archive-date"
    )
    def list_archive_dates(self, request, *args, **kwargs):
        dates = Post.objects.dates("created_time", "month", order="DESC")
        date_field = DateField()
        data = [date_field.to_representation(date) for date in dates]
        return Response(data=data, status=status.HTTP_200_OK)

注意这里我们涉及到了几个以前没有详细讲解过的用法。

一是 action 装饰器,它用来装饰一个视图集中的方法,被装饰的方法会被 django-rest-framework 的路由自动注册为一个 API 接口。

回顾一下我们之前在使用视图集 viewset 时提到过 action(动作)的概念,django-rest-framework 预定义了几个标准的动作,分别为 list 获取资源列表,retrieve 获取单个资源、update 和 partial_update 更新资源、destroy 删除资源,这些 action 具体的实现方法,分别由 mixins 模块中的混入类提供。例如 用类视图实现首页 API 中我们介绍过 mixins.ListModelMixin ,这个混入类提供了 list 动作对应的标准实现,即 list 方法。视图集中所有以上提及的以标准动作命名的方法,都会被 django-rest-framework 的路由自动注册为标准的 API 接口。

django-rest-framework 默认只能识别标准命名的视图集方法并将其注册为 API,但我们可以添加更多非标准的 action,而为了让 django-rest-framework 能够识别这些方法,就需要使用 action 装饰器进行装饰。

其实我们可以简单地将 action 装饰的方法看作是一个视图函数的实现,因此可以看到方法传入的第一个参数为 request 请求对象,函数体就是这个视图函数需要执行的逻辑,显然,方法最终必须要返回一个 HTTP 响应对象。

action 装饰器通常用于在视图集中添加额外的接口实现。例如这里我们已有了 PostViewSet 视图集,标准的 list 实现了获取文章资源列表的逻辑。我们想添加一个获取文章归档日期列表的接口,因此添加了一个 list_archive_dates 方法,并使用 action 进行装饰。通常如果要在视图集中添加额外的接口实现,可以使用如下的模板代码:

@action(
    methods=["allowed http method name"], 
    detail=False or True, 
    url_path="url/path", 
    url_name="url name"
)
def method_name(self, request, *args, **kwargs):
    # 接口逻辑的具体实现,返回一个 Response

通常 action 装饰器以下 4 个参数都会设置:

methods:一个列表,指定访问这个接口时允许的 HTTP 方法(GET、POST、PUT、PATCH、DELETE)

detail:True 或者 False。设置为 True,自动注册的接口 URL 中会添加一个 pk 路径参数(请看下面的示例),否则不会。

url_path:自动注册的接口 URL。

url_name:接口名,主要用于通过接口名字反解对应的 URL。

当然,我们还可以在 action 中设置所有 ViewSet 类所支持的类属性,例如 serializer_classpagination_classpermission_classes 等,用于覆盖类视图中设置的属性值。

以上是 action 用法的一个基本介绍,现在来分析一下 list_archive_dates 这个 action 来加深理解。

methods 参数指定接口需要通过 GET 方法访问,detail 为 Falseurl_path 设置为 archive/dates,因此最终自动生成的接口路由就是 /posts/archive/dates/。如果我们设置 detail 为 True,那么生成的接口路由就是 /posts/<int:pk>/archive/dates/ ,生成的 URL 中就会多一个 pk 路径参数。

list_archive_dates 具体的实现逻辑中,以下几点需要注意:

一是独立使用序列化字段(Field)。之前序列化字段都是在序列化器(Serializer)里面使用的,因为通常来说接口需要序列化一个对象的多个字段。而这个接口中只需要序列化一个时间字段(类型为 Python 标准库中的 datetime.date ),所以没必要单独定义一个序列化器了,直接拿 django-rest-framework 提供的用于序列化时间类型的 DateField 就可以了。用法也很简单,实例化序列化字段,调用其 to_representation 方法,将需要序列化的值传入即可(其实序列化器在序列对象的多个字段时,内部也是分别调用对应序列化字段的 to_representation 方法)。

我们通过列表推导式生成一个序列化后的归档日期列表,这个列表是可被序列化的。接着我们在接口返回一个 ResponseResponse 将序列化后的结果包装返回(保存在 data 属性中),django-rest-framework 会进一步帮我们把这个 Response 中包含的数据解析为合适的格式(例如 JSON)。

status=status.HTTP_200_OK 指定这个接口返回的状态码, HTTP_200_OK 是一个预定义的常数,即 200。django-rest-framework 将常用 HTTP 请求的状态码常数预定义 status 模块里,使用预定义的变量而不是直接使用数字的好处一是增强代码可读性,二是减少硬编码。

由于 PostViewSet 视图集已经通过 django-rest-framework 的路由进行了注册,因此 list_archive_dates 也会被连带着自动注册为一个接口。启动开发服务器,访问 /posts/archive/dates/,就可以看到返回的文章归档日期列表。

![文章归档日期返回结果]( https://blog-1253812787.cos.ap-chengdu.myqcloud.com/ I73iqqV.png!web

.png)

注意到红框圈出部分,django-rest-framework API 交互后台会识别到额外定义的 action 并将它们展示出来,点击就可以进入到相应的 API 页面。

现在,侧边栏所需要的数据接口就开发完成了,接下来实现返回某一分类、标签或者归档日期下的文章列表接口。

使用视图集简化代码 我们开发了获取全部文章的接口。事实上,分类、标签或者归档日期文章列表的 API,本质上还是返回一个文章列表资源,只不过比首页 API 返回的文章列表资源多了个“过滤”,只过滤出了指定的部分文章而已。对于这样的场景,我们可以在请求 API 时加上查询参数,django-rest-framework 解析查询参数,然后从全部文章列表中过滤出查询所指定的文章列表再返回。

这在 RESTful API 的设计中肯定是会遇到的,因此第三方库 django-filter 帮我们实现了上述所说的查询过滤功能,而且和 django-rest-framework 有很好的集成,我们可以在 django-rest-framework 中非常方便地使用 django-filter。

既然要使用它,当然是先安装它(已安装跳过): pipenv install django-filter

接着我们来配置 PostViewSet ,为其设置用于过滤返回结果集的一些属性,代码如下:

from django_filters.rest_framework import DjangoFilterBackend
from .filters import PostFilter

class PostViewSet(
    mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
    # ...
    filter_backends = [DjangoFilterBackend]
    filterset_class = PostFilter

非常的简单,仅仅设置了 filter_backendsfilterset_class 两个属性。其中 filter_backends 设置为 DjangoFilterBackend ,这样 API 在返回结果时, django-rest-framework 会调用设置的 backend(这里是 DjangoFilterBackend ) 的 filter 方法对 get_queryset 方法返回的结果进行进一步的过滤,而 DjangoFilterBackend 会依据 filterset_class (这里是 PostFilter )中定义的过滤规则来过滤查询结果集。

当然 PostFilter 还没有定义,我们来定义它。首先在 blog 应用下创建一个 filters.py 文件,用于存放自定义 filter 的代码, PostFilter 代码如下:

from django_filters import rest_framework as drf_filters

from .models import Post


class PostFilter(drf_filters.FilterSet):
    created_year = drf_filters.NumberFilter(
        field_name="created_time", lookup_expr="year"
    )
    created_month = drf_filters.NumberFilter(
        field_name="created_time", lookup_expr="month"
    )

    class Meta:
        model = Post
        fields = ["category", "tags", "created_year", "created_month"]

PostFilter 的定义和序列化器 Serializer 非常类似。

categorytags 两个过滤字段因为是 Post 模型中定义的字段,因此 django-filter 可以自动推断其过滤规则,只需要在 Meta.fields 中声明即可。

归档日期下的文章列表,我们设计的接口传递 2 个查询参数:年份和月份。由于这两个字段在 Post 中没有定义, Post 记录时间的字段为 created_time ,因此我们需要显示地定义查询规则,定义的规则是:

查询参数名 = 查询参数值的类型(查询的模型字段,查询表达式)

例如示例中定义的 created_year 查询参数,查询参数值的类型为 number,即数字,查询的模型字段为 created_time ,查询表达式是 year 。当用户传递 created_year 查询参数时,django-filter 实际上会将以上定义的规则翻译为如下的 ORM 查询语句:

Post.objects.filter(created_time__year=created_year传递的值)

现在回到 API 交互后台,先进到 /post/ 接口下,默认返回了全部文章列表。可以看到右上角多了个过滤器(红框圈出部分)。

rA3uYbU.png!web

点击会弹出过滤参数输入的交互面板,在这里可以交互式地输入查询过滤参数的值。

YZ7fQzz.png!web

例如选择如下的过滤参数,得到查询的 URL 为:

http://127.0.0.1:10000/api/posts/?category=1&tags=1&created_year=2020&created_month=1

这条查询返回创建于 2020 年 1 月,id 为 1 的分类下,id 为 1 的标签下的全部文章。

通过不同的查询参数组合,就可以得到不同的文章资源列表了。

ARJJ733.png!web

关注公众号加入交流群


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK