DJango--DRF基础教程
DRF是Django的扩展,用于实现 Restful 提供了序列化器 Serializer 、更多的视图类、Mixin 扩展类,且自带接口测试文档。
安装
pip install djangorestframework==3.12.4
配置
# settings.py
INSTALLED_APPS = [
'rest_framework'
]
# 默认值见rest_framework.settings.py
REST_FRAMEWORK = {
# API渲染
# 'DEFAULT_RENDERER_CLASSES': ( # 默认响应渲染类
# 'rest_framework.renderers.JSONRenderer', # json渲染器
# 'rest_framework.renderers.BrowsableAPIRenderer', # 浏览API渲染器
# ),
# 分页
# 'DEFAULT_PAGINATION_CLASS': None,
# 'PAGE_SIZE': None,
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', # 启用分页
'PAGE_SIZE': 10,
# 过滤
# 'DEFAULT_FILTER_BACKENDS': [],
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
# 版本控制
# 'DEFAULT_VERSIONING_CLASS': 'apps.core.middleware.version_control.CustomVersioning',
# 登录
# 'DEFAULT_AUTHENTICATION_CLASSES': [ # 身份认证
# 'rest_framework.authentication.SessionAuthentication',
# 'rest_framework.authentication.BasicAuthentication'
# ],
# 'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
'UNAUTHENTICATED_USER': None, # 未认证请求的request.user。默认配置依赖django.contrib.auth,因此要禁用
# 'UNAUTHENTICATED_TOKEN': None,
# 权限
# 'DEFAULT_PERMISSION_CLASSES': [
# 'rest_framework.permissions.AllowAny', # 允许所有用户请求
# ],
# 限流拦截
# 'DEFAULT_THROTTLE_CLASSES': [],
# 'DEFAULT_THROTTLE_RATES': {
# 'user': None,
# 'anon': None,
# },
# 错误处理
# 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
}
路由
DRF 改良了 URLConf 配置的写法,除原本的配置方式外,还支持 routers 方式
直接引入
# urls.py
from rest_framework import routers
import article.views
router = routers.DefaultRouter()
router.register(r'article', article.views.ArticleViewSet)
urlpatterns = [
re_path(r"^api/", include(router.urls)),
]
间接引入
其中basename用于视图反向获取url
# article/router.py
from rest_framework.routers import DefaultRouter
import article.views
article_router = DefaultRouter()
article_router.register("admin_article", article.views.AdminArticleViewSet, basename="admin_article")
article_router.register("article", article.views.ArticleView, basename="article")
# urls.py
from rest_framework.routers import DefaultRouter
from article.router import article_router
router = DefaultRouter()
router.registry.extend(article_router.registry)
urlpatterns = [
re_path(r"^api/", include(route.urls))
]
Serializer(序列化与反序列化)
序列化:把模型对象(many=True时则为QuerySet)转换成字典
反序列化:对字典进行校验,并转成对象。
serializers.Serializer
可用于任意类的序列化(不局限于Django模型类)
构造函数:init(self, instance=None, data=empty, **kwargs)
其中 instance 为实例对象,data 为字典,实例化后亦可通过这两个字段取值。
此外可支持传入其他配置参数,如:
- many=True 则表示传入instace为查询集QuerySet
- partial=True 表示将只传入部分字段用于校验,忽略其他字段的required=True
- context 用于传入一个字典,供 Serializer
类中方法通self.context[‘XXX’]取值,使用get_serializer()方法时会自动为context加入以下内容:
'request': self.request,
'format': self.format_kwarg,
'view': self
字段类型
布尔
- BooleanField()
- NullBooleanField()
表示布尔值,也接受None为有效值。
字符串
- CharField(max_length=None, min_length=None, allow_blank=False,
trim_whitespace=True)对应同名model 或 models.TextField - EmailField(max_length=None, min_length=None, allow_blank=False)
- RegexField(regex, max_length=None, min_length=None,
allow_blank=False)用于验证正则是否有效 - SlugField(maxlength=50, min_length=None, allow_blank=False)
一个验证输入满足表达式[a-zA-Z0-9_-]+的RegexField字段。 - URLField(max_length=200, min_length=None, allow_blank=False)
一个验证输入满足URL格式的RegexField字段。要求使用以下格式的完全限定网址http:///,使django.core.validators.URLValidator进行验证。 - UUIDField(format=’hex_verbose’)
确保输入为有效UUID字符串的字段。该to_internal_value方法将返回一个uuid.UUID实例。 - IPAddressField(protocol=’both’, unpack_ipv4=False, **options)
数字
- IntegerField(max_value=None, min_value=None)
- FloatField(max_value=None, min_value=None)
- DecimalField(max_digits, decimal_places, coerce_to_string=None,
max_value=None, min_value=None)
日期时间
- DateTimeField(format=“%Y-%m-%d %H:%M:%S”, input_formats=None) format
代表输出格式的字符串,默认值为api_settings.DATETIME_FORMAT input_formats
用于解析日期的输入格式的字符串列表
如果model中启动auto_now或auto_now_add则此处自动启用read_only=True。 - DateField(format=“%Y-%m-%d %H:%M:%S”, input_formats=None)
- TimeField(format=“%Y-%m-%d %H:%M:%S”, input_formats=None)
- DurationField()
选择字段
- ChoiceField(choices) 对应model中配置的choices项,用于自动生成该字段allow_blank和allow_null可用于选择结果的空处理
- MultipleChoiceField(choices)
文件上传字段
- FileField(max_length=None, allow_empty_file=False,
use_url=UPLOADED_FILES_USE_URL) max_length 为文件名最大长度 - ImageField(max_length=None, allow_empty_file=False,
use_url=UPLOADED_FILES_USE_URL)
需要安装Pillow包或PIL包。推荐使用Pillow包,因为PIL包已经不积极维护。
复合字段
- ListField(child, min_length=None, max_length=None)
一个列表,其中child用于验证列表中对象的字段实例
scores = serializers.ListField(child=serializers.IntegerField(min_value=0, max_value=100))
- DictField(child) 一个字典,其中child用于验证字典中对象的字段实例
document = DictField(child=CharField())
- JSONField()
关联字段
- StringRelatedField(many=False) 返回一个调用对应关系的 str
方法的字符串。强制read_only=True。
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.StringRelatedField(many=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
{
'album_name': 'Things We Lost In The Fire',
'artist': 'Low',
'tracks': [
'1: Sunflower',
'2: Whitetail',
'3: Dinosaur Act',
...
]
}
- PrimaryKeyRelatedField 即SlugRelatedField(slug_field=‘主键key’)特化版
- SlugRelatedField(required=True, read_only=False, slug_field=‘id’,
queryset=Tenant.objects.filter(is_delete=0), error_messages={“null”:
“请选择租户”, “required”: “缺少租户”, “does_not_exist”: “租户不存在”})通常对应model中配置的外键项 queryset:如不配置该项,则必须有read_only=True many:一对多时应配置为True,allow_null:是否允许为空
自定义方法字段
SerializerMethodField(method_name=None)
method_name - 要调用的序列化程序上的方法的名称。如果未包括,则默认为get_<field_name>。
字段通用参数
read_only 表明该字段仅用于序列化输出,默认False
write_only 表明该字段仅用于反序列化输入,默认False
required 表明该字段在反序列化时必须输入,默认True
default 反序列化时使用的默认值
allow_null 表明该字段是否允许传入None,默认False(注意即使model里null=True,此处依然默认False)
validators 该字段使用的验证器
label 用于HTML展示API页面时,显示的字段名称
help_text 用于HTML展示API页面时,显示的字段帮助提示信息
error_messages 包含错误编号与错误信息的字典
内置方法、属性
- to_representation(self, instance) 设置全局序列化逻辑,可用于增加/修改返回值
- get_字段名(self, attrs) 设置 SerializerMethodField 类型字段的序列化逻辑。可用于增加/修改返回值
- validate(self, attrs) 设置全局反序列化逻辑
- validate_字段名(self, attrs) 指定单独字段的反序列化逻辑
- is_valid() 进行反序列化校验,返回布尔值
可设置raise_exception=True,则校验失败时抛出serializers.ValidationError,并向前端返回HTTP
400 Bad Request - validated_data 反序列化结果值,必须在执行is_valid()校验成功后,再获取该属性。
- data model实例的序列化结果值字典
- instance 序列化时传入的实例(没有则为None,可用于区分新增和编辑)
- save() 调用该Serializer类中的create或update方法
如serializer.save(create_user_id=request.user.id, level=level)
# serializers.py
from rest_framework import serializers
class ArticleAdminSerializer(serializers.Serializer):
content = serializers.CharField(required=True, trim_whitespace=False, max_length=20, error_messages={"required": "请输入内容", "blank": "内容不能为空", "null": "内容不能为空", "max_length": "太长了,小伙子"})
is_show_home = serializers.ChoiceField(required=False, choices=((0, '否'), (1, '是')), error_messages={"invalid_choice": "选择是否首页展示", "null": "是否首页展示不能为空"})
key = serializers.CharField(required=False, allow_null=True, allow_blank=True)
name_by_method = serializers.SerializerMethodField()
def get_name_by_method(self, attrs):
return attrs.name
def validate_key(self, attrs):
if attrs:
queryset = Article.objects.filter(is_delete=0,
key__iexact=attrs)
if self.instance is not None:
queryset = queryset.exclude(pk=self.instance.id)
if queryset.exists():
raise VException(500, '关键字已存在')
return attrs
# views.py
serializer = ArticleAdminSerializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
form_data = serializer.validated_data
serializers.ModelSerializer
继承自Serializer,允许通过Meta配置模型类,并自动生成其字段及validators,且包含默认的create()和update()实现
通过extra_kwargs可为字段增加或修改validators
也可通过显式声明覆写model中字段的validators,或新增更多字段。
通过为每个filed配置source属性可以修改其在model中的映射字段,默认即同名字段。
# serializers.py
from rest_framework import serializers
from article.models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article # 对应表
# fields = ('name', 'content') # 仅指定部分字段
# fields = "__all__" # 所有字段
exclude = ('id',) # 仅忽略部分字段
extra_kwargs = {
'name': {'max_length': 3,
'error_messages': {"max_length": "长度太长啦"}
# 'write_only': True,
# 'required': True
},
}
- 注意:model中的外键不会自动生成字段和validators,需要手动设置。
# 此处CheckOffCode有外键activity
class CheckOffCodeSerializer(serializers.ModelSerializer):
activity_id = serializers.IntegerField(required=True, error_messages={"required": "缺少活动"})
class Meta:
model = CheckOffCode
fields = "__all__"
def validate(self, attrs):
activity_id = attrs.get("activity_id")
if activity_id:
if not Activity.objects.filter(is_delete=0, id=activity_id).exists():
raise serializers.ValidationError("活动不存在")
return attrs
serializers.HyperlinkedModelSerializer
与 ModelSerializer 相比,通过新增的url字段代替主键或外键,方便前端直接通过该 url 访问其对应资源而无需自行拼接路径。
在validate中区分新增和编辑
- 方法一:
if self.instance is not None:
queryset = queryset.exclude(pk=self.instance.id)
if queryset.exists():
raise VException(500, '角色名已存在')
- 方法二(需配合context使用):
id = self.context['view'].kwargs.get("pk", None)
name_in_db = Product.objects.filter(name=name,is_delete=0).exclude(id=id).first()
if(name_in_db):
raise VException(500, '已存在名称和规格相同的产品')
请求与响应
DRF的各个视图类均继承自View,重写了as_view方法,并对 request 做了封装。
request
DRF做了封装(rest_framework.request.Request),提供了一些额外属性。
- data
返回解析之后的请求体数据字典。类似于Django中标准的request.POST和 request.FILES属性,但提供如下特性:
包含了解析之后的文件和非文件数据
包含了对POST、PUT、PATCH请求方式解析后的数据
利用了REST framework的parsers解析器,不仅支持表单类型数据,也支持JSON数据 - query_params
即Django标准的request.GET - user
根据认证策略返回用户信息。默认认证用户为 django.contrib.auth.models.User 实例,未认证用户为django.contrib.auth.models.AnonymousUser 实例。
Response
封装了新的Response类,集成了Django的各个返回类型
Response(data, status=None, template_name=None, headers=None, content_type=None)
入参
- data:传入字典等Python内建类型,会自动被处理
- status:默认为200
- template_name:渲染模板名
- content_type:通常无需传入,自动根据前端header的accept生成
返回值属性
- data 传入data的序列化结果
- content 传入data的render结果
- status_code 状态码数字
from rest_framework.response import Response
...
return Response({'data': data}, status=status.HTTP_200_OK)
可以修改默认的响应器类型
# settings.py
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': ( # 默认响应渲染类
'rest_framework.renderers.JSONRenderer', # json渲染器
# 'rest_framework.renderers.BrowsableAPIRenderer', # 浏览API渲染器
),
}
视图
-
views.APIView
继承自View,是DRF所有视图的基类,实现了上述 request 和 Response 的封装,增加了异常捕获,并在dispatch()前对请求进行身份认证、权限检查、流量控制。 -
viewsets.GenericViewSet
较常使用,封装了对象获取、序列化、分页等功能
应与Mixin类配合使用,并为其提供一些封装好的属性和方法: -
queryset:指明使用的数据查询集(如model.objects.all())
-
serializer_class:指明视图使用的序列化器
-
pagination_class:指明视图使用的分页控制器,默认采用全局配置。详见分页
-
filter_backends:指明视图所用的过滤控制器,默认采用全局配置。详见过滤
-
get_queryset:获取配置的 queryset,Mixin中会使用该方法
-
get_serializer:获取并执行配置的 serializer_class,Mixin中会使用该方法
-
get_object:获取一个model实例对象,Mixin中会使用该方法
默认使用APIView.check_object_permissions方法检查当前对象的访问权限 -
filter_queryset:传入一个queryset,并基于 filter_backends 对其进行筛选
-
lookup_field:指定一个属性,代替默认的id作为路由中的主键被检索
-
viewsets.ModelViewSet
即 GenericViewSet + 下述五种Mixin
mixins
mixins.CreateModelMixin(成功则返回201)
mixins.RetrieveModelMixin
mixins.UpdateModelMixin
mixins.DestroyModelMixin(成功则返回204)
mixins.ListModelMixin
需配合GenericViewSet使用,提供create(), retrieve(), update(), partial_update(), destroy(), list() 等动作方法
自定义action
- 通过路由配置
# urls.py
urlpatterns = [
path('test/', views.Test.as_view({'get':'details'}))
]
- 通过装饰器配置
- methods必传
- detail必传,为True表示对应单一资源,则url为 url_path//
形式。(pk对应的属性由lookup_field配置项决定) - url_path非必传,默认为下方方法名,且允许使用正则匹配(同urls.py规则)
- 其他配置项可用于覆盖View类的配置
# views.py
from rest_framework.decorators import action
...
@action(methods=['get'], detail=False, url_path='reject/(?P<table>\w+)/(?P<pk>\w+)', pagination_class=None, filter_backends=None)
def reject(self, request, table, pk, *args, **kwargs):
print(table, pk)
print(self.action) # details
return Response("hello world")
分页
默认不启用,修改全局配置可统一启用分页功能
# settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', # 使用系统自带的分页
'PAGE_SIZE': 10, # 使用 PageNumberPagination 时必须配置该项
}
视图GenericViewSet子类中也可以单独指定或禁用分页
# views.py
from rest_framework.pagination import PageNumberPagination
class ArticleViewSet(viewsets.GenericViewSet):
pagination_class = PageNumberPagination # 单独指定分页方式
pagination_class = None # 禁用分页
}
启用分页后,视图支持以下方法:
- paginate_queryset:根据配置的 pagination_class,处理传入的 queryset ,返回带有分页信息的
queryset - get_paginated_response:传入带分页信息的serializer.data,返回带分页信息的Response实例,DRF自带如下几种分页,可以基于其创建子类以实现自定义分页
三种分页模式
- PageNumberPagination 普通分页 根据入参 page 指定页数(从1开始)
- LimitOffsetPagination 偏移分页 根据入参,从第 offset 条数据,往后返回 limit 条数据
- CursorPagination 游标分页 可传入
size。每次请求会返回cursor,下次请求传入cursor即可从上一次返回的位置继续向下查找
过滤排序
默认不启用,修改全局配置可统一配置过滤类,或在视图GenericViewSet子类中配置
SearchFilter
用于模糊查询 search_fields 中字段
OrderingFilter
用于对前端传入的 ordering_fields 中字段排序,或通过配置ordering元组指定默认排序
# settings.py
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': [],
}
# views.py
from rest_framework.filters import SearchFilter, OrderingFilter
class ArticleViewSet(viewsets.GenericViewSet):
filter_backends = [SearchFilter, OrderingFilter] # 配置过滤类
search_fields = ["name", "content"] # 配置要过滤的字段
ordering_fields = ["id", "name"] # 配置要排序的字段
ordering = ("-id", "name") # 配置默认的排序字段
}
# 前端请求
/api/article/?search=你好&ordering=-id
DjangoFilterBackend
通过第三方包 django_filters 引入
- 通过filterset_fields指定支持查询的字段(精准查询)
pip install django-filter
# settings.py
INSTALLED_APPS = [
...
'django_filters', # 需要注册应用,
]
REST_FRAMEWORK = {
...
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}
class ArticleViewSet(viewsets.GenericViewSet):
# filter_backends = [DjangoFilterBackend] # 配置过滤类
filterset_fields=['name']
}
# 前端请求
/api/article/?name=你好
- 通过filter_class自定义查询规则
# views.py
class RockRecordView(viewsets.GenericViewSet):
filter_backends = [DjangoFilterBackend]
filter_class = RockRecordFilter
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
# filter.py
from django_filters import rest_framework as filters
from django_filters import BaseInFilter
class RockRecordFilter(filters.FilterSet):
wall = filters.CharFilter(lookup_expr='icontains')
type_in = BaseInFilter(field_name='route__type', lookup_expr='in')#接收数据如1,2字符串
audit_status = filters.NumberFilter(lookup_expr='exact')
start_time = filters.DateTimeFilter(field_name="created_time", lookup_expr='gte')
end_time = filters.DateTimeFilter(field_name="created_time", lookup_expr='lte')
认证、权限、频率
DRF视图中自动执行了三大验证:认证—>权限—>频率
可通过配置修改默认的认证,及未认证请求的 request.user 和 request.auth 值
# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
# 'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
'UNAUTHENTICATED_USER': None, # 未认证请求的request.user。默认配置依赖django.contrib.auth,因此要禁用
'UNAUTHENTICATED_TOKEN': None,
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny', # 允许所有用户请求
],
}
频率限制(默认不开启)
可根据认证情况配置:
# settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': (
'rest_framework.throttling.UserRateThrottle', # 使用 DEFAULT_THROTTLE_RATES['user']
'rest_framework.throttling.AnonRateThrottle', # 使用 DEFAULT_THROTTLE_RATES['anon']
),
'DEFAULT_THROTTLE_RATES': {
'user': '1000/day', # 已登录用户访问频次一天1000次
'anon': '3/m', # 未登录用户访问频次一分钟3次
}
}
也可以根据视图配置:
# views.py
class ContactListView(APIView):
throttle_scope = 'contacts'
...
class ContactDetailView(APIView):
throttle_scope = 'contacts'
...
class UploadView(APIView):
throttle_scope = 'uploads'
...
# settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': (
'rest_framework.throttling.ScopedRateThrottle',
),
'DEFAULT_THROTTLE_RATES': {
'contacts': '1000/day',
'uploads': '20/day'
}
}
错误处理
DRF默认的异常处理,只处理APIException及其子类的异常,其他返回None。
通常可改用自定义方法,加入log日志,并为非APIException的异常添加处理和返回值。
# settings.py
REST_FRAMEWORK = {
# 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
'EXCEPTION_HANDLER': 'apps.core.framework.exception.custom_exception_handler',
}
# exception.py
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
response = exception_handler(exc, context)
return response
Swagger配置
参考文档: https://github.com/axnsan12/drf-yasg
pip install drf_yasg
# settings.py
INSTALLED_APPS = [
...
'drf_yasg', # swagger接口生成工具
]
# SWAGGER配置
SWAGGER_SETTINGS = {
# 'PERSIST_AUTH': True,
# 'REFETCH_SCHEMA_WITH_AUTH': False,
# 'REFETCH_SCHEMA_ON_LOGOUT': False,
'USE_SESSION_AUTH': False,
# 'SECURITY_DEFINITIONS': {
# 'Bearer': {
# 'type': 'apiKey',
# 'name': 'Access-Token',
# 'in': 'header'
# },
# }
}
# urls.py
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
from rest_framework.permissions import AllowAny
schema_view = get_schema_view(
openapi.Info(
title="我的Swagger",
default_version='v1.0.0',
description="Test Description",
terms_of_service="https://github.com/TwinkleLee",
contact=openapi.Contact(email="624061283@qq.com"),
license=openapi.License(name="BSD License"),
),
public=True,
permission_classes=(AllowAny,),
authentication_classes=()
)
urlpatterns = [
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
re_path(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]
默认根据GenericViewSet类下的serializer_class等为接口生成Swagger,也可通过@swagger_auto_schema装饰器装饰视图方法,以自定义文档参数:
@swagger_auto_schema(
operation_description="后管操作信息",
manual_parameters=[openapi.Parameter(name='group', in_=openapi.IN_QUERY, description="group", required=True,
type=openapi.TYPE_STRING)],
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['name', 'value'],
properties={
'name': openapi.Schema(type=openapi.TYPE_STRING, description='名称'),
'value': openapi.Schema(type=openapi.TYPE_NUMBER, description='值'),
},
),
responses={200: openapi.Response('description', serializer_class)},
responses={200: "自定义描述"},
security=[],#表示该请求无需token验证
tags=['operate_log'],
)
正式环境访问/swagger时静态文件404问题
访问/swagger时页面的静态文件(js、css)是drf_yasg/static目录下的,当django debug=True 时,会自动映射到项目static下,但是当debug=False时,需要手动进行映射操作:
1.执行python manage.py collectstatic,自动搜索INSTALLED_APPS 的app的静态文件,并将其复制到项目的STATIC_ROOT目录下(通常即static目录)
2.在urls.py的urlpatterns中,添加url(r’^static/(?P