时间:2020.09.15
环境:Python3.7 Django==3.0.8 djangorestframework==3.11.0
目的:快速开发web应用啊
说明:笔记
作者:Zhong QQ交流群:121160124 欢迎加入!
目录
DRF简介
Django REST framework 框架是一个用于构建Web API 的强大而又灵活的工具。
通常简称为DRF框架 或 REST framework。
DRF框架是建立在Django框架基础之上 所以前提是需要安装Django环境
安装Django环境
pip install -i https://pypi.douban.com/simple django
安装DRF
pip install -i https://pypi.douban.com/simple djangorestframework
在settings.py文件 INSTALLED_APPS 选项中增加DRF应用配置 这儿名称为rest_framework
INSTALLED_APPS = [
...
'rest_framework',
]
DRF认证
JWT
JWT 即Json Web Token 我们使用JWT来认证请求用户
安装JWT插件 这儿使用推荐使用djangorestframework-simplejwt 因为djangorestframework-jwt据说已经不更新了
pip install -i https://pypi.douban.com/simple djangorestframework-simplejwt
在settings.py中添加配置项
# DRF一般的配置项在此REST_FRAMEWORK对象配置中定义
REST_FRAMEWORK = {
# 默认认证类 是一个列表或元祖对象
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
],
}
# JWT相关配置
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': datetime.timedelta(hours=8), # 访问token有效期8小时
'REFRESH_TOKEN_LIFETIME': datetime.timedelta(days=30), # 刷新token有效期30天
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': True,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUDIENCE': None,
'ISSUER': None,
'AUTH_HEADER_TYPES': ('Bearer',),
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
# 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
# 'TOKEN_TYPE_CLAIM': 'token_type',
'JTI_CLAIM': 'jti',
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': datetime.timedelta(minutes=5),
'SLIDING_TOKEN_REFRESH_LIFETIME': datetime.timedelta(days=1),
}
配置url 在根url文件urls.py中增加JWT的签发token路由和刷新token路由的项
urlpatterns = [
path('admin/', admin.site.urls),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^api-token-auth/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
url(r'^api-token-refresh/', TokenRefreshView.as_view(), name='token_refresh'),
url(r'^api/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
]
启动项目 使用postman测试 输入url地址:http://127.0.0.1:8000/api-token-auth/ 选择POST方法 在Body选择form-data 传入username和password参数 alice为数据库中已存在的用户
发送请求后返回access字段和refresh字段 access字段就是包含了alice用户信息的JWT加密后的字符串 可以使用它在请求API时进行身份验证 由于在配置文件中设置了8小时有效期 所以8小时后此token将失效 需要携带refresh token来刷新access token
编写一个简单的视图 请求用户列表 如果不使用JWT 将会提示如下
这是因为启用了JWT认证 而请求中并没有发送使用任何验证用户身份的识别信息 所以要通过验证 需要使用access token 因为在JWT配置中指定了AUTH_HEADER_TYPES为Bearer 所以在postman的Authorization项中配置TYPE为Bearer Token 并写入通过http://127.0.0.1:8000/api-token-auth/路由请求到的access 再次发送请求
如果是前后端分离项目 假如前端使用Vue框架的话 可以在登录通过后获取到后端返回的结果 将其存储在localStorage中 需要注意的是在发送到后端验证时JWT需要有Bearer前缀 与JWT字符串用一个空格连接 可以在存储时加上前缀 也可以在验证时加上前缀 两者选择其中一种方式即可
Login.vue
methods: {
login () {
let _this = this;
if (this.loginForm.username === '' || this.loginForm.password === '') {
alert('账号或密码不能为空');
} else {
this.$http({
method: 'post',
url: '/api-token-auth/',
data: {username: _this.loginForm.username,
password: _this.loginForm.password}
}).then(res => {
window.localStorage.setItem('access-token','Bearer ' + res.data.access)
window.localStorage.setItem('refresh-token','Bearer ' + res.data.refresh)
_this.$router.push('/index');
alert('登陆成功');
}).catch(error => {
alert('账号或密码错误');
});
}
}
}
在main.js中设置请求拦截器
// 请求拦截器
axios.interceptors.request.use(config => {
config.headers.Authorization = window.localStorage.getItem('access-token')
return config
})
DRF权限管理
认证一般是和权限配合使用的 认证让系统知道你是谁 权限管理赋予你对应身份的权限 对哪些视图拥有什么动作权限 例如:
对于未认证的匿名用户只能执行GET请求获取查看用户列表 对于已认证用户拥有POST、PUT、DELETE请求权限来执行用户增加、更新和删除操作
在settings.py文件REST_FRAMEWORK配置中增加DEFAULT_PERMISSION_CLASSES选项 这儿是全局配置
REST_FRAMEWORK = {
# 默认的认证管理类
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
],
# 默认权限级别 AllowAny 允许所有的访问 一般设置为 IsAuthenticated 允许已认证用户
'DEFAULT_PERMISSION_CLASSES': (
# 'rest_framework.permissions.AllowAny',
'rest_framework.permissions.IsAuthenticated',
),
}
此时的权限允许已通过JWT认证通过的用户执行所有的动作 为了测试 我们在视图内部定义局部的权限配置 局部配置将在生效的视图范围内覆盖全局配置 即局部配置优先级高于全局配置优先级
# 查询、更新、删除数据
class PersonnelDetail(generics.RetrieveUpdateDestroyAPIView):
authentication_classes = [authentication.JWTAuthentication]
permission_classes = [permissions.IsAuthenticatedOrReadOnly,]
queryset = STModel.objects.all()
serializer_class = ST_Serializers
def get(self, request, *args, **kwargs):
print(request.user)
print(request.auth)
return self.retrieve(request, *args, **kwargs)
此时的定义为访问此API使用JWT认证 权限为如果已认证可以GET(获取数据)、PUT(更新数据)、和DELETE(删除数据) 如果未认证即匿名用户只能GET(获取数据)
通过JWT认证
匿名用户
认证和权限管理的选项很多 可根据需求进行搭配组合使用
DRF视图
Django内置的视图类View方便我们编写代码 但DRF带来了更强大更多的方式来满足不同的开发需求 所以基本上 使用Django框架大多都会使用REST Framework扩展来提升开发效率和简化代码的逻辑
DRF有很多类和mixin扩展可使用 为了便于快速上手和区分 这儿大概将其分为3类
- APIView
- GenericAPIView(以及它的拓展和mixin)
- ViewSet
APIView
APIView是REST framework提供的所有视图的基类 继承自Django的View
父类
支持定义的属性:
- authentication_classes 列表或元祖,身份认证类
- permissoin_classes 列表或元祖,权限检查类
- throttle_classes 列表或元祖,流量控制类
在APIView
中仍以常规的类视图定义方法来实现get() 、post() 或者其他请求方式的方法。
from rest_framework.views import APIView
from rest_framework.response import Response
# url(r'^books/$', views.BookListView.as_view()),
class BookListView(APIView):
def get(self, request):
books = BookInfo.objects.all()
serializer = BookInfoSerializer(books, many=True)
return Response(serializer.data)
GenericAPIView
GenericAPIView继承自APIVIew
,增加了对于列表视图和详情视图可能用到的通用支持方法。通常使用时,可搭配一个或多个Mixin扩展类
支持定义的属性:
- 列表视图与详情视图通用:
- queryset 列表视图的查询集
- serializer_class 视图使用的序列化器
- 列表视图使用:
- pagination_class 分页控制类
- filter_backends 过滤控制后端
- 详情页视图使用:
- lookup_field 查询单一数据库对象时使用的条件字段,默认为'
pk
' - lookup_url_kwarg 查询单一数据时URL中的参数关键字名称,默认与look_field相同
- lookup_field 查询单一数据库对象时使用的条件字段,默认为'
提供的方法
列表视图与详情视图通用
-
get_queryset(self)
返回视图使用的查询集,是列表视图与详情视图获取数据的基础,默认返回
queryset
属性,可以重写,例如
def get_queryset(self):
user = self.request.user
return user.accounts.all()
- get_serializer_class(self)
返回序列化器类,默认返回serializer_class
,可以重写
def get_serializer_class(self):
if self.request.user.is_staff:
return FullAccountSerializer
return BasicAccountSerializer
- get_serializer(self, args, *kwargs)
返回序列化器对象,被其他视图或扩展类使用,如果我们在视图中想要获取序列化器对象,可以直接调用此方法
注意,在提供序列化器对象的时候,REST framework会向对象的context属性补充三个数据:request、format、view,这三个数据对象可以在定义序列化器时使用
详情视图使用
-
get_object(self) 返回详情视图所需的模型类数据对象,默认使用
lookup_field
参数来过滤queryset。 在试图中可以调用该方法获取详情信息的模型类对象。若详情访问的模型类对象不存在,会返回404。
该方法会默认使用APIView提供的check_object_permissions方法检查当前对象是否有权限被访问。
# url(r'^books/(?P<pk>\d+)/$', views.BookDetailView.as_view()),
class BookDetailView(GenericAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
def get(self, request, pk):
book = self.get_object()
serializer = self.get_serializer(book)
return Response(serializer.data)
五个扩展类
1)ListModelMixin
列表视图扩展类,提供list(request, *args, **kwargs)
方法快速实现列表视图,返回200状态码。
该Mixin的list方法会对数据进行过滤和分页。
源代码:
class ListModelMixin(object):
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
# 过滤
queryset = self.filter_queryset(self.get_queryset())
# 分页
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
# 序列化
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
举例:
from rest_framework.mixins import ListModelMixin
class BookListView(ListModelMixin, GenericAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
def get(self, request):
return self.list(request)
2)CreateModelMixin
创建视图扩展类,提供create(request, *args, **kwargs)
方法快速实现创建资源的视图,成功返回201状态码。
如果序列化器对前端发送的数据验证失败,返回400错误。
源代码:
class CreateModelMixin(object):
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
# 获取序列化器
serializer = self.get_serializer(data=request.data)
# 验证
serializer.is_valid(raise_exception=True)
# 保存
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
3) RetrieveModelMixin
详情视图扩展类,提供retrieve(request, *args, **kwargs)
方法,可以快速实现返回一个存在的数据对象。
如果存在,返回200, 否则返回404。
源代码:
class RetrieveModelMixin(object):
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
# 获取对象,会检查对象的权限
instance = self.get_object()
# 序列化
serializer = self.get_serializer(instance)
return Response(serializer.data)
举例:
class BookDetailView(RetrieveModelMixin, GenericAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
def get(self, request, pk):
return self.retrieve(request)
4)UpdateModelMixin
更新视图扩展类,提供update(request, *args, **kwargs)
方法,可以快速实现更新一个存在的数据对象。
同时也提供partial_update(request, *args, **kwargs)
方法,可以实现局部更新。
成功返回200,序列化器校验数据失败时,返回400错误。
源代码:
class UpdateModelMixin(object):
"""
Update a model instance.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
5)DestroyModelMixin
删除视图扩展类,提供destroy(request, *args, **kwargs)
方法,可以快速实现删除一个存在的数据对象。
成功返回204,不存在返回404。
源代码:
class DestroyModelMixin(object):
"""
Destroy a model instance.
"""
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance):
instance.delete()
几个可用子类视图
1) CreateAPIView
提供 post 方法
继承自: GenericAPIView、CreateModelMixin
2)ListAPIView
提供 get 方法
继承自:GenericAPIView、ListModelMixin
3)RetireveAPIView
提供 get 方法
继承自: GenericAPIView、RetrieveModelMixin
4)DestoryAPIView
提供 delete 方法
继承自:GenericAPIView、DestoryModelMixin
5)UpdateAPIView
提供 put 和 patch 方法
继承自:GenericAPIView、UpdateModelMixin
6)RetrieveUpdateAPIView
提供 get、put、patch方法
继承自: GenericAPIView、RetrieveModelMixin、UpdateModelMixin
7)RetrieveUpdateDestoryAPIView
提供 get、put、patch、delete方法
继承自:GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestoryModelMixin
ViewSet
使用视图集ViewSet,可以将一系列逻辑相关的动作放到一个类中:
- list() 提供一组数据
- retrieve() 提供单个数据
- create() 创建数据
- update() 保存数据
- destory() 删除数据
ViewSet视图集类不再实现get()、post()等方法,而是实现动作 action 如 list() 、create() 等。
视图集只在使用as_view()方法的时候,才会将action动作与具体请求方式对应上。
例如
class BookInfoViewSet(viewsets.ViewSet):
def list(self, request):
...
def retrieve(self, request, pk=None):
...
在设置路由时,我们可以如下操作
urlpatterns = [
url(r'^books/$', BookInfoViewSet.as_view({'get':'list'}),
url(r'^books/(?P<pk>\d+)/$', BookInfoViewSet.as_view({'get': 'retrieve'})
]
action属性
在视图集中,我们可以通过action对象属性来获取当前请求视图集时的action动作是哪个。
def get_serializer_class(self):
if self.action == 'create':
return OrderCommitSerializer
else:
return OrderDataSerializer
常用视图集父类
- ViewSet
继承自APIView
,作用也与APIView基本类似,提供了身份认证、权限校验、流量管理等。
在ViewSet中,没有提供任何动作action方法,需要我们自己实现action方法。
- GenericViewSet
继承自GenericAPIView
,作用也与GenericAPIVIew类似,提供了get_object、get_queryset等方法便于列表视图与详情信息视图的开发。
- ModelViewSet
继承自GenericAPIVIew
,同时包括了ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin。
- ReadOnlyModelViewSet
继承自GenericAPIVIew
,同时包括了ListModelMixin、RetrieveModelMixin。
视图集中定义附加action动作
在视图集中,除了上述默认的方法动作外,还可以添加自定义动作。
添加自定义动作需要使用rest_framework.decorators.action
装饰器。
以action装饰器装饰的方法名会作为action动作名,与list、retrieve等同。
action装饰器可以接收两个参数:
- methods: 该action支持的请求方式,列表传递
- detail: 表示是action中要处理的是否是视图资源的对象(即是否通过url路径获取主键)
- True 表示使用通过URL获取的主键对应的数据对象
- False 表示不使用URL获取主键
例如
from rest_framework import mixins
from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action
class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
# detail为False 表示不需要处理具体的BookInfo对象
@action(methods=['get'], detail=False)
def latest(self, request):
"""
返回最新的图书信息
"""
book = BookInfo.objects.latest('id')
serializer = self.get_serializer(book)
return Response(serializer.data)
# detail为True,表示要处理具体与pk主键对应的BookInfo对象
@action(methods=['put'], detail=True)
def read(self, request, pk):
"""
修改图书的阅读量数据
"""
book = self.get_object()
book.bread = request.data.get('read')
book.save()
serializer = self.get_serializer(book)
return Response(serializer.data)
url的定义
urlpatterns = [
url(r'^books/$', views.BookInfoViewSet.as_view({'get': 'list'})),
url(r'^books/latest/$', views.BookInfoViewSet.as_view({'get': 'latest'})),
url(r'^books/(?P<pk>\d+)/$', views.BookInfoViewSet.as_view({'get': 'retrieve'})),
url(r'^books/(?P<pk>\d+)/read/$', views.BookInfoViewSet.as_view({'put': 'read'})),
]
视图集的继承关系
路由Routers
对于视图集ViewSet,我们除了可以自己手动指明请求方式与动作action之间的对应关系外,还可以使用Routers来帮助我们快速实现路由信息。
REST framework提供了两个router
- SimpleRouter
- DefaultRouter
使用方法
创建router对象,并注册视图集
from rest_framework import routers
router = routers.SimpleRouter()
router.register(r'books', BookInfoViewSet, base_name='book')
register(prefix, viewset, base_name)
- prefix 该视图集的路由前缀
- viewset 视图集
- base_name 路由名称的前缀
如上述代码会形成的路由如下
^books/$ name: book-list
^books/{pk}/$ name: book-detail
添加路由数据
可以有两种方式
urlpatterns = [
...
]
urlpatterns += router.urls
或
urlpatterns = [
...
url(r'^', include(router.urls))
]
视图集中包含附加action的
class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
@action(methods=['get'], detail=False)
def latest(self, request):
...
@action(methods=['put'], detail=True)
def read(self, request, pk):
...
此视图集会形成的路由
^books/latest/$ name: book-latest
^books/{pk}/read/$ name: book-read
路由router形成URL的方式
SimpleRouter
DefaultRouter
DefaultRouter与SimpleRouter的区别是,DefaultRouter会多附带一个默认的API根视图,返回一个包含所有列表视图的超链接响应数据
视图方法对比
APIView代码量相对来说是最多最直观的 灵活性可定制性最佳 开发时间也最长
GenericAPIView代码量适中 灵活性也是中等 可以根据不同的需求使用不同的扩展类
ViewSet是常用类高度集成 抽象度最高 代码最为简洁 效率高但也不容易理解和定制特殊的需求的方法
其它
限流
过滤
Filtering
对于列表数据可能需要根据字段进行过滤,我们可以通过添加django-fitlter扩展来增强支持。
pip install -i https://pypi.douban.com/simple django-filter
在配置文件中增加过滤后端的设置:
INSTALLED_APPS = [
...
'django_filters', # 需要注册应用,
]
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}
在视图中添加filter_fields属性,指定可以过滤的字段
class BookListView(ListAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
filter_fields = ('btitle', 'bread')
# 127.0.0.1:8000/books/?btitle=西游记
排序
分页
分页Pagination
REST framework提供了分页的支持 可以在配置文件中设置全局的分页方式
REST_FRAMEWORK = {
......
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10 # 每页数目
}
也可通过自定义Pagination类,来为视图添加不同分页行为
class GenericSetPagination(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
max_page_size = 10000
在视图中通过pagination_class
属性来指明
class BookDetailView(RetrieveAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
pagination_class = GenericSetPagination
注意:如果在视图内关闭分页功能,只需在视图内设置
pagination_class = None
使用PageNumberPagination分页器 url应如 http://127.0.0.1:8000/api/books/?page=1&page_size=10
全局配置时默认page参数是可用的 page_size是未启用的 可以通过自定义分页器指定属性page_size_query_param = 'page_size' 或者其它字符串来启用
可选分页器
PageNumberPagination
前端访问网址形式: GET http://api.example.org/books/?page=4
可以在子类中定义的属性:
- page_size 每页数目
- page_query_param 前端发送的页数关键字名,默认为"page"
- page_size_query_param 前端发送的每页数目关键字名,默认为None
- max_page_size 前端最多能设置的每页数量
from rest_framework.pagination import PageNumberPagination
class StandardPageNumberPagination(PageNumberPagination):
page_size_query_param = 'page_size'
max_page_size = 10
class BookListView(ListAPIView):
queryset = BookInfo.objects.all().order_by('id')
serializer_class = BookInfoSerializer
pagination_class = StandardPageNumberPagination
# 127.0.0.1/books/?page=1&page_size=2
LimitOffsetPagination
前端访问网址形式:GET http://api.example.org/books/?limit=10&offset=40
可以在子类中定义的属性:
- default_limit 默认限制,默认值与
PAGE_SIZE
设置一致 - limit_query_param limit参数名,默认'limit'
- offset_query_param offset参数名,默认'offset'
- max_limit 最大limit限制,默认None
from rest_framework.pagination import LimitOffsetPagination
class BookListView(ListAPIView):
queryset = BookInfo.objects.all().order_by('id')
serializer_class = BookInfoSerializer
pagination_class = LimitOffsetPagination
# 127.0.0.1:8000/books/?offset=3&limit=2
异常处理
自动生成接口文档
微信公众号