本文会继续上一篇文章《DRF系列总结一:DRF是什么,要不要用?》,在Django基础工程的基础上,安装DRF并进行配置:比如统一接口返回格式
、统一异常处理
等,并在后面的文章中,不断完善出一套DRF脚手架
,以降低后面的开发同学的趟坑
成本。
首先,我们创建一个Django基础工程demo
,并创建一个测试app,得到了Django框架的初始化代码,代码目录结构如下:
# 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,按照官方文档进行操作:
pip install djangorestframework
pip install markdown # Markdown support for the browsable API.
pip install django-filter # Filtering support
备注:安装DRF时,请留意周边版本依赖,比如:
3.10.2版本依赖
接下来开始配置DRF:
rest_framework
加入到INSTALLED_APPS
中,修改文件settings.py
: INSTALLED_APPS += (
...
'rest_framework',
...
)
settings.py
,增加如下配置: # BEP-DRF
# =================================================
# DRF 全局配置区,默认配置见:rest_framework.settings
# =================================================
REST_FRAMEWORK = {
}
DRF优先从django配置文件中的REST_FRAMEWORK
字典中获取配置信息,获取不到则使用DRF的默认配置:
...
@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的默认配置都有哪些:
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基础策略
、视图侧配置
、后台分页
、异常处理
等几个部分,接下来我们开始自定义配置:
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客户端;身份验证成功后,会得到以下凭据:
- `request.user` 是一个 Django User 实例
- `request.auth` 是 None
未经身份验证的请求会返回`403`配置全局过滤器
REST_FRAMEWORK = {
...
# 全局表查询过滤器
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
],
...
}
INSTALLED_APPS += (
...
'django_filters', # for filtering rest endpoints
...
)
通过引入django_filters的DjangoFilterBackend
,我们可以通过配置的方式对外快速提供Django模型的查询接口,且接口的参数格式类似DjangoORM的语法,比如:
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的视图类中增加以下配置(具体配置参见文档),即可实现name
、code
、is_activated
三个字段的综合查询接口:/systems/?name__contains=平台&code__in=cc,bb&is_activated=1
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接口的项目来说,真是个好东西,简单配置一下,接口就都有了。
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提供的一个分页类的基础上,进行了简单的改造,内容如下:
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),
]))
改造后的接口分页格式,增加了page
和total_page
字段,代表当前页和总页数,并修改了返回数据的字段为items
,这样可以统一接口的分页格式,满足前端和第三方系统对接口后台分页的绝大部分需求场景。
接口格式统一是开发规范的一个基本要求,比如:
{
"result": true,
"data": [],
"message": "success",
"code": 0
}
DRF的接口一般会直接返回创建的数据或者数据列表,如图所示:
于是,结合开发规范对接口的要求,我们需要对DRF的返回格式进行统一处理
首先,我们简单看下DRF的视图类关系:
视图类的派生关系
View ----> APIView --------> GenericAPIView
|-ViewSet |-GenericViewSet
|-ModelViewSet
父类->子类
View ----> APIView --------> GenericAPIView
+ finalize_response (统一接口返回格式)
我们的接口基本上都是通过继承ModelViewSet
提供的,通过阅读代码和文档,我们发现ModelViewSet
的父类APIView
中的finalize_response
函数恰好是DRF定义的response统一处理的接口,于是我们可以重写ModelViewSet
的这个函数来实现格式统一,并且让我们的视图类都继承修改过的ModelViewSet
即可。
然后,我们简单修改了下ModelViewSet
:
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
)
并定义了接口返回时间字段的格式:
REST_FRAMEWORK = {
...
# 时间字段序列化格式
'DATETIME_FORMAT': "%Y-%m-%d %H:%M:%S",
'DATETIME_INPUT_FORMATS': "%Y-%m-%d %H:%M:%S",
...
}
我们对不符合格式要求的response数据进行了规范化,继承自这个以后,我们可以得到规范化的接口:
...
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"
})
return Response([1, 2, 3])
这里推荐将API部分接口的路由单独拎出来,比如以/api/
开头的路由到DRF提供的接口中:
而在具体app的路由中,直接使用DRF的router
模块,并将视图视图注册到路由中即可:
注册完以后,我们就可以通过:/api/demo/
开头的地址访问接口了
到这里,你可能已经发现,这个东西的配置成本还是有的。前面我们主要讲了如何安装DRF,接着介绍了如何配置DRF,并将自己项目中的经验总结在了里面,希望能对后面的DRFers有所帮助。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。