前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >DRF系列总结二:脚手架搭建

DRF系列总结二:脚手架搭建

原创
作者头像
高木工
发布2020-06-16 12:32:10
3.6K0
发布2020-06-16 12:32:10
举报
文章被收录于专栏:运维开发运维开发

  本文会继续上一篇文章《DRF系列总结一:DRF是什么,要不要用?》,在Django基础工程的基础上,安装DRF并进行配置:比如统一接口返回格式统一异常处理等,并在后面的文章中,不断完善出一套DRF脚手架,以降低后面的开发同学的趟坑成本。

一、安装DRF

  首先,我们创建一个Django基础工程demo,并创建一个测试app,得到了Django框架的初始化代码,代码目录结构如下:

代码语言:txt
复制
# django-admin startproject demo
# django-admin startapp app
.
├── app
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── demo
│   ├── __init__.py
│   ├── settings.py       # 配置
│   ├── urls.py             # 路由
│   └── wsgi.py
└── manage.py

  上面,我省略了与配置DRF无关的一些目录,重点关注demo目录,这个目录专门提供给开发者进行工程配置settings.py。可以根据环境拆分配置文件,比如dev.py/stag.py/prod.py(本文就当做重点来展开了),urls.py用于配置路由,app则是一个普通的Django应用,方便快速开发。

  接下来,我们开始安装DRF,按照官方文档进行操作:

代码语言:txt
复制
pip install djangorestframework
pip install markdown       # Markdown support for the browsable API.
pip install django-filter  # Filtering support

备注:安装DRF时,请留意周边版本依赖,比如:

3.10.2版本依赖
3.10.2版本依赖

二、配置DRF

  接下来开始配置DRF:

  1. rest_framework 加入到INSTALLED_APPS中,修改文件settings.py
代码语言:txt
复制
    INSTALLED_APPS += (
        ...
        'rest_framework',
        ...
    )
  1. 配置DRF框架,修改文件settings.py,增加如下配置:
代码语言:txt
复制
    # BEP-DRF
    # =================================================
    # DRF 全局配置区,默认配置见:rest_framework.settings
    # =================================================
    REST_FRAMEWORK = {
    }

DRF优先从django配置文件中的REST_FRAMEWORK字典中获取配置信息,获取不到则使用DRF的默认配置:

代码语言:txt
复制
   ...
        @property
        def user_settings(self):
            if not hasattr(self, '_user_settings'):
                self._user_settings = getattr(settings, 'REST_FRAMEWORK', {})
            return self._user_settings

        def __getattr__(self, attr):
            if attr not in self.defaults:
                raise AttributeError("Invalid API setting: '%s'" % attr)

            try:
                # Check if present in user settings
                val = self.user_settings[attr]
            except KeyError:
                # Fall back to defaults
                val = self.defaults[attr]
    ...

我们先看下DRF的默认配置都有哪些:

代码语言:txt
复制
DEFAULTS = {
    # Base API policies
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
...
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication'
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ],
...
    # Generic view behavio
    'DEFAULT_PAGINATION_CLASS': None,
    'DEFAULT_FILTER_BACKENDS': [],
...
    # Pagination
    'PAGE_SIZE': None,
...
    # Exception handling
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
    'NON_FIELD_ERRORS_KEY': 'non_field_errors',
...
    # Input and output formats
    'DATETIME_FORMAT': ISO_8601,
    'DATETIME_INPUT_FORMATS': [ISO_8601],
...
}

上面是它的默认配置,这里只保留了和我们自定义配置相关的部分(省略部分可以直接看源码),包括API基础策略视图侧配置后台分页异常处理等几个部分,接下来我们开始自定义配置:

  • 配置接口认证和权限
代码语言:txt
复制
    REST_FRAMEWORK = {
    ...
    # 身份认证机制:采用Django的认证机制
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        # 'rest_framework.authentication.BasicAuthentication',
],

    # 接口权限设置:仅支持登录用户访问
    'DEFAULT_PERMISSION_CLASSES': [
        # 'rest_framework.permissions.AllowAny',
        'rest_framework.permissions.IsAuthenticated',
    ],
...
    }

这里的接口权限策略,去掉了匿名用户的读取权限,仅允许经过身份验证的注册用户访问接口;

这里的接口认证策略,去掉了HTTP基本认证的方式(接口提供账号密码),仅保留了使用Django默认session后端进行身份验证的机制,适用于与网站在相同的Session环境中运行的AJAX客户端;身份验证成功后,会得到以下凭据:

代码语言:txt
复制
- `request.user` 是一个 Django User 实例
- `request.auth` 是 None
    未经身份验证的请求会返回`403`配置全局过滤器
代码语言:txt
复制
  REST_FRAMEWORK = {
    ...
    # 全局表查询过滤器
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend',
    ],
    ...
    }
    
    INSTALLED_APPS += (
        ...
        'django_filters',  # for filtering rest endpoints
        ...
    )

通过引入django_filtersDjangoFilterBackend,我们可以通过配置的方式对外快速提供Django模型的查询接口,且接口的参数格式类似DjangoORM的语法,比如:

代码语言:txt
复制
    class RemoteSystem(Model):
    """
    第三方系统配置表
    """
    system_id = models.IntegerField(_('系统id'), default=0)
    name = models.CharField(_('系统名称'), max_length=LEN_NORMAL, null=False)
    code = models.CharField(_('系统编码'), max_length=LEN_NORMAL, null=False)
    desc = models.CharField(_('系统描述'), max_length=LEN_LONG, default=EMPTY_STRING)
    is_activated = models.BooleanField(_('是否启用'), default=False)

只需要在DRF的视图类中增加以下配置(具体配置参见文档),即可实现namecodeis_activated三个字段的综合查询接口:/systems/?name__contains=平台&code__in=cc,bb&is_activated=1

代码语言:txt
复制
    class RemoteSystemViewSet(ModelViewSet):
    """系统视图"""
    serializer_class = RemoteSystemSerialize
    queryset = RemoteSystem.objects.all()

    # django_filters配置语法示例
    # /systems/?name__contains=平台&code__in=cc,bb&is_activated=1
    filter_fields = {
        "name": ["exact", "contains", "startswith"],
        "code": ["exact", "in"],
        "is_activated": ["exact"],
    }
...

django_filters对于需要对外提供Django模型的CRUD接口的项目来说,真是个好东西,简单配置一下,接口就都有了。

  • 自定义后台分页格式
代码语言:txt
复制
    REST_FRAMEWORK = {
    ...
    # 全局分页设置
    # 'DEFAULT_PAGINATION_CLASS': None,
    'DEFAULT_PAGINATION_CLASS': 'component.drf.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,
    ...
    }

PAGE_SIZE全局设置了默认的分页属性:单页数据量

DEFAULT_PAGINATION_CLASS设置为None时,则关闭了所有列表接口的后台分页功能,我们这里提供了自定义分页类component.drf.pagination.PageNumberPagination供大家参考。我们在DRF提供的一个分页类的基础上,进行了简单的改造,内容如下:

代码语言:txt
复制
        from collections import OrderedDict

        from rest_framework import pagination
        from rest_framework.response import Response
        from rest_framework.settings import api_settings

        class PageNumberPagination(pagination.PageNumberPagination):
            """
                自定义分页格式,返回当前页码和总页数
                http://api.example.org/accounts/?page=4
                http://api.example.org/accounts/?page=4&page_size=100
            """

            page_size = api_settings.PAGE_SIZE

            # 定义分页参数名
            page_size_query_param = 'page_size'
            page_query_param = 'page'

            # 定义单页最多返回条数
            max_page_size = 3000

            def get_paginated_response(self, data):
                return Response(OrderedDict([
                    ('count', self.page.paginator.count),
                    ('next', self.get_next_link()),
                    ('previous', self.get_previous_link()),
                    # 修改字段名:results->items
                    # ('results', data)
                    ('items', data),
                    ('page', self.page.number),
                    ('total_page', self.page.paginator.num_pages),
                ]))

改造后的接口分页格式,增加了pagetotal_page字段,代表当前页和总页数,并修改了返回数据的字段为items,这样可以统一接口的分页格式,满足前端和第三方系统对接口后台分页的绝大部分需求场景。

  • 统一接口返回

接口格式统一是开发规范的一个基本要求,比如:

代码语言:txt
复制
{
    "result": true,
    "data": [],
    "message": "success",
    "code": 0
}

DRF的接口一般会直接返回创建的数据或者数据列表,如图所示:

DRF接口返回一
DRF接口返回一
DRF接口返回二
DRF接口返回二

于是,结合开发规范对接口的要求,我们需要对DRF的返回格式进行统一处理

首先,我们简单看下DRF的视图类关系:

代码语言:txt
复制
    视图类的派生关系
    View ----> APIView --------> GenericAPIView
                |-ViewSet         |-GenericViewSet
                                    |-ModelViewSet
    父类->子类
    View ----> APIView --------> GenericAPIView
                + finalize_response (统一接口返回格式)

我们的接口基本上都是通过继承ModelViewSet提供的,通过阅读代码和文档,我们发现ModelViewSet的父类APIView中的finalize_response函数恰好是DRF定义的response统一处理的接口,于是我们可以重写ModelViewSet的这个函数来实现格式统一,并且让我们的视图类都继承修改过的ModelViewSet即可。

然后,我们简单修改了下ModelViewSet

代码语言:txt
复制
class ModelViewSet(viewsets.ModelViewSet):
    """定制ModelViewSet"""

    _keys = {'result', 'code', 'message', 'data'}

    def finalize_response(self, request, response, *args, **kwargs):
        """强制统一接口返回格式:
            {
                'result': True/False,
                "code": 0,
                "message": 'success',
                "data": '',
            }
        """

        data = response.data
        status_code = response.status_code

        if not isinstance(data, dict):
            response.data = {
                'result': True,
                "code": 0,
                "message": 'success',
                "data": response.data,
            }
        elif not set(data.keys()).issuperset(self._keys):
            # success: 200~299
            result = is_success(status_code)
            # code = 0 or status_code
            code = 0 if result else status_code
            if result:
                message = data.get('message', 'success')
            else:
                message = data.get('_err_msg', data.get('detail', 'failed'))
                data.pop('_err_msg', None)

            response.data = {
                'result': result,
                "code": code,
                "message": message,
                "data": response.data,
            }

        return super(ModelViewSet, self).finalize_response(
            request, response, *args, **kwargs
        )

并定义了接口返回时间字段的格式:

代码语言:txt
复制
    REST_FRAMEWORK = {
    ...
     # 时间字段序列化格式
    'DATETIME_FORMAT': "%Y-%m-%d %H:%M:%S",
    'DATETIME_INPUT_FORMATS': "%Y-%m-%d %H:%M:%S",
    ...
    }

我们对不符合格式要求的response数据进行了规范化,继承自这个以后,我们可以得到规范化的接口:

代码语言:txt
复制
       ...
       return Response({
            "name": "更新集群",
            "path": "/api/c/compapi/v2/cc/update_set/",
            "version": "v2",
            "func_name": "update_set",
            "method": "POST",
            "desc": "更新集群",
            "create_at": "2019-08-31 19:37:48"
        })
代码语言:txt
复制
    return Response([1, 2, 3])
  • 路由配置

这里推荐将API部分接口的路由单独拎出来,比如以/api/开头的路由到DRF提供的接口中:

根目录下的urls.py
根目录下的urls.py

而在具体app的路由中,直接使用DRF的router模块,并将视图视图注册到路由中即可:

app中的urls.py
app中的urls.py

注册完以后,我们就可以通过:/api/demo/开头的地址访问接口了

demo urls
demo urls

三、总结

  到这里,你可能已经发现,这个东西的配置成本还是有的。前面我们主要讲了如何安装DRF,接着介绍了如何配置DRF,并将自己项目中的经验总结在了里面,希望能对后面的DRFers有所帮助。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、安装DRF
  • 二、配置DRF
  • 三、总结
相关产品与服务
多因子身份认证
多因子身份认证(Multi-factor Authentication Service,MFAS)的目的是建立一个多层次的防御体系,通过结合两种或三种认证因子(基于记忆的/基于持有物的/基于生物特征的认证因子)验证访问者的身份,使系统或资源更加安全。攻击者即使破解单一因子(如口令、人脸),应用的安全依然可以得到保障。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档