DRF视图和常用功能
DRF视图
DRF视图类介绍
在DRF框架中提供了众多的通用视图基类与扩展类,以简化视图的编写。
-
View:Django默认的视图基类,负责将视图连接到URL,HTTP请求方法的基本调度,之前写类视图一般都用这个。
-
APIView:DRF提供的所有视图的基类,继承View并扩展,具备了身份认证、权限检查、流量控制等功能。
-
GenericAPIView:对APIView更高层次的封装,例如增加分页、过滤器
-
GenericViewSet:继承GenericAPIView和ViewSet
-
ViewSet:继承APIView,并结合router自动映射路由
-
ModelViewSet:继承GenericAPIView和五个扩展类,封装好各种请求,更加完善,业务逻辑基本不用自己写了。
APIView类
APIView:DRF提供的所有视图的基类,继承View并扩展,具备了身份认证、权限检查、流量控制等功能
- 创建项目
- 创建app并加入settings.py
E:\workspace\django-project\day2\django_drf>python manage.py startapp myapp
- 项目主路由配置: django_drf/urls.py
- 创建项目视图: myapp/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
class UserAPIView(APIView):
def get(self,request):
data = {'result': 'get'}
return Response(data)
def post(self,request):
data = {'result': 'post'}
return Response(data)
def put(self,request):
data = {'result': 'put'}
return Response(data)
def delete(self,request):
data = {'result': 'delete'}
return Response(data)
- 项目路由配置: myapp/urls.py
from django.urls import re_path
from myapp import views
urlpatterns = [
re_path('^users1/$', views.UserAPIView.as_view())
]
- 项目启动测试
- 浏览器访问测试: http://127.0.0.1:8000/myapp/users1/
Request与Response
Request
DRF传入视图的request对象不再是Django默认的HttpRequest对象,而是基于HttpRequest类扩展后的Request类的对象。
Request对象的数据是自动根据前端发送的数据统一解析数据格式。
- 常用属性:
- request.data:返回POST提交的数据,与request.POST类似
- request.query_params:返回GET URL参数,与request.GET类似
- 浏览器get测试: http://127.0.0.1:8000/myapp/users1/?a=123&b=456
- 查看get_params数据
- post数据测试
- 查看request.data数据
get和post提交数据场景和特点
- get
- 获取数据
- 提交数据有长度限制
- 传输是明文形式
- post
- 提交数据
- 提交数据无长度限制
- 请求body中提交数据,安全系数较高
Response
DRF提供了一个响应类Reponse,响应的数据会自动转换符合前端的JSON数据格式。
导入:
from rest_framework.response import Response
格式:
Response(data, status=None, template_name=None, headers=None, content_type=None)
- data:响应序列化处理后的数据,传递python对象
- status:状态码,默认200
- template_name:模板名称
- headers:用于响应头信息的字典
- content_type:响应数据的类型
使用方法:
return Reponse(data=data, status=status.HTTP_404_NOT_FOUND)
为了方便设置状态码,rest_framework.status模块提供了所有HTTP状态码,以下是一些常用的:
HTTP_200_OK:请求成功
HTTP_301_MOVED_PERMANENTLY:永久重定向
HTTP_302_FOUND:临时重定向
HTTP_304_NOT_MODIFIED:请求的资源未修改
HTTP_403_FORBIDDEN:没有权限访问
HTTP_404_NOT_FOUND:页面没有发现
HTTP_500_INTERNAL_SERVER_ERROR:服务器内部错误
HTTP_502_BAD_GATEWAY:网关错误
HTTP_503_SERVICE_UNAVAILABLE:服务器不可达
HTTP_504_GATEWAY_TIMEOUT:网关超时
GenericAPIView
- GenericAPIView对APIView更高层次的封装,实现以下功能:
- 增加queryset属性,指定操作的数据,不用再将数据传给序列化器,会自动实现。
- 增加serializer_class属性,直接指定使用的序列化器
- 增加过滤器属性:filter_backends
- 增加分页属性:pagination_class
- 增加lookup_field属性和实现get_object()方法:用于获取单条数据,可自定义默认分组名(pk)
- 基于上面的项目新增app模型: myapp/models.py
from django.db import models
class User(models.Model):
name = models.CharField(max_length=32)
city = models.CharField(max_length=64)
sex = models.CharField(max_length=8)
age = models.IntegerField()
- 新增app序列化: myapp/serializers.py
from myapp.models import User
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
- 新增测试数据
E:\workspace\django-project\day2\django_drf>python manage.py makemigrations
E:\workspace\django-project\day2\django_drf>python manage.py migrate
E:\workspace\django-project\day2\django_drf>python manage.py shell
In [1]: from myapp.models import User
In [2]: User.objects.create(name="张三",city="北京市",sex="男",age=29)
In [3]: User.objects.create(name="李四",city="上海市",sex="女",age=19)
In [4]: User.objects.create(name="阿山",city="广州市",sex="女",age=23)
In [5]: User.objects.create(name="阿妖",city="深圳市",sex="男",age=21)
- 创建app视图类: myapp/views.py
from myapp.models import User
from rest_framework.generics import GenericAPIView
from .serializers import UserSerializer
from rest_framework.response import Response
class UserGenericAPIViews(GenericAPIView):
queryset = User.objects.all() #指定操作的数据
serializer_class = UserSerializer #指定序列化器
lookup_field = 'id'
def get(self,request, id=None):
if id:
user_obj = self.get_object() #从类方法调用指定数据,不需要传id
user_ser = self.get_serializer(user_obj)
else:
quertset = self.get_queryset() #从类方法调用所有数据
user_ser = self.get_serializer(quertset, many=True) #从类方法调用序列化器
result = {"code": 200, "msg": "success", "data": user_ser.data}
return Response(result)
def post(self,request):
user_ser = self.get_serializer(data=request.data)
user_ser.is_valid(raise_exception=True)
user_ser.save()
result = {"code": 200, "msg": "create success"}
return Response(result)
def put(self,request,id=None):
user_obj = self.get_object()
user_ser = self.get_serializer(instance=user_obj,data=request.data)
user_ser.is_valid(raise_exception=True)
result = {"code": 200, "msg": "update success"}
return Response(result)
def delete(self,request,id=None):
user_obj = self.get_object()
user_obj.delete()
result = {"code": 200, "msg": "delete success"}
return Response(result)
- 创建app路由: myapp/urls.py
from django.urls import re_path
from myapp import views
urlpatterns = [
re_path('^users1/$', views.UserAPIView.as_view()),
re_path('^users2/$',views.UserGenericAPIViews.as_view()), #获取所有数据和创建
re_path('^users2/(?P<id>\d+)/$',views.UserGenericAPIViews.as_view()), #获取单条数据及其更新,删除
]
- 功能测试
http://127.0.0.1:8000/myapp/users2/ #list,post
http://127.0.0.1:8000/myapp/users2/5/ #指定id数据的更新,删除,查看
ViewSet类
GenericAPIView已经完成了许多功能,但会有一个问题,获取所有用户列表和单个用户需要分别定义两个视图和URL路由,使用ViewSet可以很好解决这个问题,并且实现了路由自动映射
。
ViewSet视图集不再实现get()、post()等方法,而是实现以下请求方法动作:
list():获取所有数据
retrieve():获取单个数据
create():创建数据
update():更新数据
destory():删除数据
- 修改app视图类: myapp/views.py
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
from myapp.models import User
from .serializers import UserSerializer
class UserViewSet(ViewSet):
lookup_field = 'id' #自动注册路由的时候需要用这个
def list(self,request):
user_obj = User.objects.all()
user_ser = UserSerializer(user_obj, many=True)
result = {"code": 200, "msg": "success", "data": user_ser.data}
return Response(result)
def retrieve(self,request,id=None):
user_obj = User.objects.get(id=id)
user_ser = UserSerializer(user_obj)
result = {"code": 200, "msg": "success", "data": user_ser.data}
return Response(result)
def update(self,request,id=None):
user_obj = User.objects.get(id=id)
user_ser = UserSerializer(instance=user_obj,data=request.data)
user_ser.is_valid(raise_exception=True)
user_ser.save()
result = {"code": 200, "msg": "update success"}
return Response(result)
def create(self,request):
user_ser = UserSerializer(data=request.data)
user_ser.is_valid(raise_exception=True)
user_ser.save()
result = {"code": 200, "msg": "create success"}
return Response(result)
def destory(self,request,id=None):
user_obj = User.objects.get(id=id)
user_obj.delete()
result = {"code": 200, "msg": "delete success"}
return Response(result)
- 修改app的路由: myapp/urls.py
from django.urls import re_path
from myapp import views
urlpatterns = [
re_path('^users1/$', views.UserAPIView.as_view()),
re_path('^users2/$',views.UserGenericAPIViews.as_view()), #获取所有数据和创建
re_path('^users2/(?P<id>\d+)/$',views.UserGenericAPIViews.as_view()), #获取单条数据及其更新,删除
re_path('^users3/$',views.UserViewSet.as_view({'get': 'list','post': 'create'})), #获取所有数据和创建
re_path('^users3/(?P<id>\d+)/$',views.UserViewSet.as_view({'get': 'retrieve','put': 'update','delete': 'destory'})), #获取单条数据及其更新,删除
]
- 功能测试
http://127.0.0.1:8000/myapp/users3/ #list,post
http://127.0.0.1:8000/myapp/users3/5/ #指定id数据的更新,删除,查看
路由自动注册
- 修改app路由: myapp/urls.py
from django.urls import re_path,include
from myapp import views
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('user4',views.UserViewSet,'user4')
router.register('user5',views.UserViewSet,'user5') #再次注册
urlpatterns = [
re_path('^users1/$', views.UserAPIView.as_view()),
re_path('^users2/$',views.UserGenericAPIViews.as_view()), #获取所有数据和创建
re_path('^users2/(?P<id>\d+)/$',views.UserGenericAPIViews.as_view()), #获取单条数据及其更新,删除
re_path('^users3/$',views.UserViewSet.as_view({'get': 'list','post': 'create'})), #获取所有数据和创建
re_path('^users3/(?P<id>\d+)/$',views.UserViewSet.as_view({'get': 'retrieve','put': 'update','delete': 'destory'})), #获取单条数据及其更新,删除
re_path('^api/',include(router.urls))
]
- 测试功能
http://127.0.0.1:8000/myapp/api/ #查看注册的url列表
http://127.0.0.1:8000/myapp/api/user4/ #list,post
http://127.0.0.1:8000/myapp/api/user4/2/ #指定id数据的更新,删除,查看
ModelViewSet类
由于ModelViewSet有较高的抽象,实现自动增删改查功能。对于增、改在很多场景无法满足需求,这就需要重写对应方法了。
- 示例:重写create()方法,修改数据和响应内容格式
- 新增app视图: myapp/views.py
from rest_framework.viewsets import ModelViewSet
class UserModelViewSet(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
- app路由注入: myapp/urls.py
from django.urls import re_path,include
from myapp import views
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('user4',views.UserViewSet,'user4')
router.register('user5',views.UserViewSet,'user5')
router.register('user6',views.UserModelViewSet,'user6') #注入ModelViewSet
urlpatterns = [
re_path('^users1/$', views.UserAPIView.as_view()),
re_path('^users2/$',views.UserGenericAPIViews.as_view()), #获取所有数据和创建
re_path('^users2/(?P<id>\d+)/$',views.UserGenericAPIViews.as_view()), #获取单条数据及其更新,删除
re_path('^users3/$',views.UserViewSet.as_view({'get': 'list','post': 'create'})), #获取所有数据和创建
re_path('^users3/(?P<id>\d+)/$',views.UserViewSet.as_view({'get': 'retrieve','put': 'update','delete': 'destory'})), #获取单条数据及其更新,删除
re_path('^api/',include(router.urls))
]
- 接口测试
http://127.0.0.1:8000/myapp/api/ #查看注册的url列表
http://127.0.0.1:8000/myapp/api/user6/ #list,create
http://127.0.0.1:8000/myapp/api/user6/ #update, delete, retrieve
DRF常用功能
主流认证方式
Session认证
HTTP是一个无状态的协议,每次访问都是新的,早期主要用于浏览网页,随着时代发展,像在线购物网站的兴起,就面临着记录哪些人登录系统,哪些人购物车里放了商品。也就是必须每个人区分开,所以就有了用户名来标识,但每次访问页面都要登录,非常麻烦,这就有了会话保持。Cookie+Session就是实现会话保持的技术。
Token
Cookie+Session通常在浏览器作为客户端的情况下比较通用,随着前后端分离开发模式的普及,会涉及到多端(PC、APP、Pad),特别是手机端,支持Cookie不友好,并且Cookie不支持跨域,因此基于这些局限性,Token逐渐主流。
JWT
与普通Token一样,都是访问资源的令牌,区别是普通Token服务端验证token信息要查询数据库验证,JWT验证token信息不用查询数据库,只需要在服务端使用密钥效验。与普通Token一样,都是访问资源的令牌,区别是普通Token服务端验证token信息要查询数据库验证,JWT验证token信息不用查询数据库,只需要在服务端使用密钥效验。
DRF认证
目前DRF可任意访问,没有任何限制,是不符合生产环境标准的,因此接下来学习认证实现访问控制。
-
DRF支持四种认证方式:
- BasicAuthentication:基于用户名和密码的认证,适用于测试
- SessionAuthentication:基于Session的认证
- TokenAuthentication:基于Token的认证
- RemoteUserAuthentication:基于远程用户的认证
-
DRF支持权限:
- IsAuthenticated:只有登录用户才能访问所有API
- AllowAny:允许所有用户
- IsAdminUser:仅管理员用户
- IsAuthenticatedOrReadOnly:登录的用户可以读写API,未登录用户只读
DRF Session 认证
- 参考文档: https://www.django-rest-framework.org/api-guide/authentication/#sessionauthentication
由于Django默认提供Session存储机制,可直接通过登录内置管理后台进行验证。当登录管理后台后,就有权限访问了。
- 新建内置登录后台密码
全局session认证
- settings.py全局配置(追加配置即可): django_drf/settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
- 重启项目
- 测试访问:http://127.0.0.1:8000/myapp/api/
- 登录后台: http://127.0.0.1:8000/admin/login/?next=/admin/
- 刷新api视图: http://127.0.0.1:8000/myapp/api/
局部(视图)session认证
- 注释全局settings.py中关于全局session认证的配置
- 修改app视图函数: myapp/views.py
from rest_framework.viewsets import ModelViewSet
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
class UserModelViewSet(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
#认证&权限配置
authentication_classes = [SessionAuthentication]
permission_classes = [IsAuthenticated]
-
视图对应的url测试: http://127.0.0.1:8000/myapp/api/user6/
-
对比 http://127.0.0.1:8000/myapp/api/user5/
Token认证
- 全局settings.py新增app: django_drf/settings.py
- 全局配置Token认证: django_drf/settings.py
REST_FRAMEWORK = {
#认证
'DEFAULT_AUTHENTICATION_CLASSES': [
#'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
],
#权限
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
- 同步token数据库表
E:\workspace\django-project\day2\django_drf>python manage.py makemigrations
E:\workspace\django-project\day2\django_drf>python manage.py migrate
- 全局urls.py配置认证接口URL: django_drf/urls.py
from django.contrib import admin
from django.urls import path,include,re_path
from rest_framework.authtoken import views
urlpatterns = [
path('admin/', admin.site.urls),
path('myapp/',include("myapp.urls")),
re_path('^auth_token/$', views.obtain_auth_token)
]
- 使用apipost生成token: http://127.0.0.1:8000/auth_token/
- 单纯访问测试: http://127.0.0.1:8000/myapp/api/user5/
-
带token测试:
curl -H "Authorization: token ab87f8ab97dd0ffdd073546dea57c794ada6995e" http://127.0.0.1:8000/myapp/api/user5/
token自定义返回信息
- 需要重写返回信息函数
- 在app项目下的utils目录中新增重写信息: myapp/utils/token_auth.py
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.response import Response
from rest_framework.authtoken.models import Token
class CustomAuthToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({'token': token.key, 'username': user.username})
- 修改全局urls中的token部分配置: django_drf/urls.py
from django.contrib import admin
from django.urls import path,include,re_path
# from rest_framework.authtoken import views
from myapp.utils.auth_token import CustomAuthToken
urlpatterns = [
path('admin/', admin.site.urls),
path('myapp/',include("myapp.urls")),
re_path('^auth_token/$', CustomAuthToken.as_view())
]
- apipost测试: http://127.0.0.1:8000/auth_token/
限流
可以对接口访问的频率进行限制,以减轻服务器压力。
应用场景:投票、购买数量等
- 官方文档: https://www.django-rest-framework.org/api-guide/throttling/#throttling
- 全局settings配置: django_drf/settings.py
REST_FRAMEWORK = {
#认证
'DEFAULT_AUTHENTICATION_CLASSES': [
#'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
],
#权限
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
#限流
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'anon': '10/minute',
'user': '10/minute'
}
}
- 重启项目
- apipost 请求1分钟内请求10次之后结果如下:
过滤
-
对于列表数据可能需要根据字段进行过滤,我们可以通过添加django-fitlter扩展来增强支持
-
参考文档: https://www.django-rest-framework.org/api-guide/filtering/
- 安装: django-filter
pip3 install django-filter
- 全局配置新增配置: djang_drf/settings.py
- 全局配置中新增过滤配置: django_drf/settings.py
REST_FRAMEWORK = {
#认证
# 'DEFAULT_AUTHENTICATION_CLASSES': [
# 'rest_framework.authentication.SessionAuthentication',
# 'rest_framework.authentication.TokenAuthentication',
# ],
#权限
# 'DEFAULT_PERMISSION_CLASSES': [
# 'rest_framework.permissions.IsAuthenticated',
# ],
#限流
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'anon': '10/minute',
'user': '10/minute'
},
#过滤配置
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}
- app视图中新增过滤字段: myapp/views.py
from rest_framework.viewsets import ModelViewSet
class UserModelViewSet(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
#过滤
filter_fields = ('sex',)
- 页面测试:http://127.0.0.1:8000/myapp/api/user6/?sex=%E7%94%B7
搜索和排序
DRF提供过滤器帮助我们快速对字段进行搜索和排序
- 继续修改app视图中新增过滤字段: myapp/views.py
from rest_framework.viewsets import ModelViewSet
from rest_framework import filters
class UserModelViewSet(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
#过滤
filter_fields = ('sex',)
#搜索&排序
filter_backends = [filters.SearchFilter,filters.OrderingFilter]
search_fields = ('name',)
order_fields = ('age',)
- 测试: http://127.0.0.1:8000/myapp/api/user6/?search=%E5%BC%A0%E4%B8%89
分页
分页是数据表格必备的功能,可以在前端实现,也可以在后端实现,为了避免响应数据过大,造成前端压力,一般在后端实现。
- 参考文档: https://www.django-rest-framework.org/api-guide/pagination/#pagination
全局分页配置
- 修改全局settings配置: django_drf/settings.py
REST_FRAMEWORK = {
#限流
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'anon': '10/minute',
'user': '10/minute'
},
#过滤配置
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
#全局分页
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 3 #三条数据一页
}
- 接口测试: http://127.0.0.1:8000/myapp/api/user6/
自定义分页配置
- 新增自定义分页配置:myapp/utils/my_pagination.py
from rest_framework.pagination import PageNumberPagination
from collections import OrderedDict
from rest_framework.response import Response
class CustomPagination(PageNumberPagination):
page_size = 10 #每页显式多少条数据
page_query_param = 'page_num' #查询第几页数据
page_size_query_param = 'page_size' #每页显式多少条
max_page_size = 50 #每一页最大显式50条
def get_paginated_response(self, data):
return Response(OrderedDict([
('total_num', self.page.paginator.count),
('next_page', self.get_next_link()),
('msg', 'success'),
('code',200),
('previous_page', self.get_previous_link()),
('data', data)
]))
- 修改全局settings配置: django_drf/settings.py
REST_FRAMEWORK = {
#限流
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'anon': '10/minute',
'user': '10/minute'
},
#过滤配置
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
# 自定义分页
'DEFAULT_PAGINATION_CLASS': 'myapp.utils.my_pagination.CustomPagination'
}
- 分页测试: http://127.0.0.1:8000/myapp/api/user6/?page_size=2&page_num=2
自动生成接口文档
由于项目开发经验欠缺或着急上线,需求不断改动,项目设计阶段定义的接口已经面目全非,这给前端开发人员参考带来一定困难,如何改善这个问题呢?
-
Swagger来了,它是一个应用广泛的REST API文档自动生成工具,生成的文档可供前端人员查看。
-
文档参考:https://django-rest-swagger.readthedocs.io/en/latest/
- 安装django-rest-swagger
pip3 install django-rest-swagger
- 修改全局settings配置: django_drf/settings.py
- 修改全局settings配置: django_drf/settings.py
REST_FRAMEWORK = {
#限流
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'anon': '10/minute',
'user': '10/minute'
},
#过滤配置
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
# 自定义分页
'DEFAULT_PAGINATION_CLASS': 'myapp.utils.my_pagination.CustomPagination',
#接口文档
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
- 配置全局路由: django_drf/urls.py
-
访问测试: http://127.0.0.1:8000/docs/
-
修复上述问题:
D:\devTools\python3\lib\site-packages\rest_framework_swagger\templates\rest_framework_swagger\index.html
将index.html中的第二行staticfiles改成static即可解决
- 重启项目测试: http://127.0.0.1:8000/docs/