DRF进阶¶
1. http请求响应¶
drf除了在数据序列化部分简写代码以外,还在视图中提供了简写操作。所以在django原有的django.views.View类基础上,drf封装了多个视图子类出来提供给我们使用。
Django REST framwork 提供的视图的主要作用:
- 控制序列化器的执行(检验、保存、转换数据)
- 控制数据库查询的执行
- 调用请求类和响应类[这两个类也是由drf帮我们再次扩展了一些功能类。]
为了方便我们学习,所以先创建一个子应用req
注册子引用:
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"stuapi",
"students",
"sers", # 序列化阶段
"req",
]
注册路由
# 子应用路由,req/urls.py
from django.urls import path, re_path
from . import views
urlpatterns = [
]
# 总路由,drfdemo/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("stuapi/", include("stuapi.urls")),
path("drfapi/", include("students.urls")),
path("sers/", include("sers.urls")),
path("req/", include("req.urls")),
]
1.1. 请求与响应¶
在学drf提供的请求与响应类之前,我们需要了解一个概念:内容协商。
内容协商:drf在django原有的基础上,新增了一个request对象内置到了drf提供的APIVIew视图类里面,并在django原有的HttpResponse响应类的基础上实现了一个子类rest_framework.response.Response响应类。这两个类,都是基于内容协商来完成数据的格式转换的。
drf中实现的内容协商流程:
DRF的request类->parser(http请求解析类->识别客户端请求头中的Content-Type来完成数据转换成->类字典(QueryDict,字典的子类)
DRF的response类->renderer(http响应渲染类)->识别客户端请求头的"Accept"来提取客户端期望的返回数据格式-> 转换成客户端的期望格式数据
注意:
django默认是没有实现内容协商的,所以我们如果希望使用内容协商,则要么手动给django提供这个功能,要么使用drf提供的视图类。
1.1.1 Request¶
drf传入视图的request对象不再是Django默认的HttpRequest对象,而是drf自己声明的Request类的对象,当然通过drf提供request对象的_request使用调用到Django提供的request对象。
drf提供了Parser(http请求解析器类),在接收到客户端的http请求后会自动根据Content-Type指明的请求数据类型(如JSON、html表单等)将请求数据进行解析,解析为类字典[QueryDict]对象保存到drf的Request对象的data属性中。
1.1.1.1 常用属性¶
1).data¶
request.data 提供了Parse解析之后的请求体数据。类似于Django中request.POST和 request.FILES、request.body属性,但提供如下特性:
- 包含了解析之后的文件和非文件数据
- 包含了对POST、PUT、PATCH请求方式解析后的数据
- 利用了REST framework的parser解析器,不仅支持表单类型数据,也支持JSON数据
2).query_params¶
query_params,查询参数,也叫查询字符串(query string )
注意:request.query_params是不区分http请求方法的,只要是地址栏?号后面的查询字符串都可以接收,结果是QueryDict格式。
request.query_params本质上就是Django提供的request.GET,只是更换了更正确的名称而已。
3)request._request¶
获取django内容的htp请求处理对象(WSGIRequest的实例对象)
1.1.1.2 基本使用¶
视图代码:
from rest_framework.views import APIView
from rest_framework.response import Response
"""
强调!
使用了drf的视图类,必须使用drf的请求与响应。同理,使用了django的视图了则使用Django的请求与响应。
Django --> View --> HttpRequest/HttpResponse/JsonResponse
Drf --> APIView --> Request/Response
在特殊情况下,drf的视图类可以使用HttpResponse返回结果,但是比较少见。
"""
class IndexAPIView(APIView):
def get(self, request):
print(request.query_params) # 获取地址栏上的查询字符串,本质上就是Django的request.GET,所以任何的Http请求,只要地址栏有查询字符串,都可以使用。
return Response({"msg": "get"})
def post(self, request):
print(request.data)
# print(request.data.dict()) # 如果客户端提交的是表单数据,则可以使用dict方法,把QueryDict转换成普通字典
return Response({"msg": "post"})
def put(self, request):
print(request.data)
return Response({"msg": "put"})
def patch(self, request):
print(request.data)
return Response({"msg": "patch"})
路由代码:
from django.urls import path, re_path
from . import views
urlpatterns = [
path("django/", views.DjangoView.as_view()),
path("drf/", views.DrfAPIView.as_view()),
path("index1/", views.IndexAPIView.as_view()),
]
通过postman可以测试。
1.1.2 Response¶
REST framework提供了一个响应类Response,使用该类实例化响应对象时,响应的具体数据内容会被renderer(http响应渲染器类)转换成符合前端期望的数据类型。
REST framework提供了Renderer 渲染器,用来根据客户端的请求头中的Accept(接收数据类型声明)来自动转换响应数据到对应格式。如果前端请求中未进行声明Accept,则会采用Content-Type方式处理响应数据,我们可以通过配置来修改默认响应格式。
可以在rest_framework/settings.py查找所有的drf默认配置项,源码:
# REST_FRAMEWORK 表示字典内部的内容是属于drf独有的配置项, 与django的配置项进行区分。
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': ( # 默认http响应渲染类
'rest_framework.renderers.JSONRenderer', # json渲染器,返回json数据
'rest_framework.renderers.BrowsableAPIRenderer', # 浏览器API渲染器,返回api调试界面
)
}
1.1.2.1 构造方式¶
drf提供的视图中要响应数据,Response类的视图代码如下:
from rest_framework.response import Response
Response(data, status=None, template_name=None, headers=None, content_type=None)
drf提供的响应处理类Response和请求处理类Request类不一样,drf提供的Response就是django的HttpResponse响应处理类的子类。
data数据不要是render处理之后的数据,只需传递python的基本数据即可,REST framework会使用renderer渲染器处理data。
data不能是复杂结构的数据,如Django的模型类对象,对于这样的数据我们可以使用Serializer序列化器序列化处理后(转为了Python字典类型)再传递给data参数。
参数说明:
data: 为响应准备的序列化处理后的数据;status: 状态码,默认200template_name: 模板名称,如果使用HTMLRenderer时需指明;headers: 用于存放响应头信息的字典;content_type: 响应数据的Content-Type,通常此参数无需传递,REST framework会根据前端所需类型数据来设置该参数。
1.1.2.2 response对象的属性¶
1).data¶
传给response对象的序列化后,但尚未render处理的数据
2).status_code¶
状态码的数字
3).content¶
经过renderer渲染类处理后的响应数据
1.1.2.3 状态码¶
为了方便设置状态码,REST framewrok在rest_framework.status模块中提供了常用http状态码的常量。
1)信息告知 - 1xx¶
2)成功 - 2xx¶
3)重定向 - 3xx¶
4)客户端错误 - 4xx¶
HTTP_400_BAD_REQUEST
HTTP_401_UNAUTHORIZED
HTTP_403_FORBIDDEN
HTTP_404_NOT_FOUND
HTTP_405_METHOD_NOT_ALLOWED
5)服务器错误 - 5xx¶
HTTP_500_INTERNAL_SERVER_ERROR
HTTP_502_BAD_GATEWAY
HTTP_503_SERVICE_UNAVAILABLE
HTTP_504_GATEWAY_TIMEOUT
HTTP_507_INSUFFICIENT_STORAGE
2. 视图¶
Django REST framwork 提供的视图的主要作用:
- 控制序列化器的执行(检验、保存、转换数据)
- 控制数据库模型的操作
2.1 视图类 APIView¶
REST framework 提供了众多的通用视图基类与扩展类,以简化视图的编写。
2.1.1 2个视图基类¶
2.1.1.1 APIView基本视图类¶
APIView是REST framework提供的所有视图类的基类,继承自Django的View视图基类。
APIView与View的不同之处在于:
-
传入到视图方法中的是REST framework的
Request对象,而不是Django的HttpRequeset对象; -
视图方法可以返回REST framework的
Response对象,视图会为响应数据设置(renderer)符合前端期望要求的格式; -
任何可以被
APIException捕获到异常,都将会被APIView处理成合适格式的响应信息返回给客户端;
django 的View中所有异常全部以HTML格式显示,不会返回json格式。
drf的APIVIew或者APIView的子类会自动根据客户端的Accept进行错误信息的格式转换。
- drf重新声明了一个新的as_view方法并在dispatch()进行路由分发前,会对请求的客户端进行身份认证、权限检查、流量控制。
APIView为了实现上面说的3个功能除了继承了View原有的属性方法意外,还新增了类属性:
- authentication_classes 值是列表或元组,成员是身份认证类
- permissoin_classes 值是列表或元组,成员是权限检查类
- throttle_classes 值是列表或元祖,成员是流量控制类
在APIView中仍以常规的类视图定义方法来实现get() 、post() 或者其他请求方式的方法。
为了方便演示,所以drf提供的视图里面的内容知识,我们另外创建一个子应用来展示
注册子应用
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'students',
'sers', # 序列化器
"school", # 序列化器嵌套
'req', # 请求与响应
'drfview', # 视图
]
总路由,代码:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('students/', include("students.urls")),
path('sers/', include("sers.urls")),
path('school/', include("school.urls")),
path("req/", include("req.urls")),
path("drfview/", include("drfview.urls")),
]
子应用路由,drfview/urls.py,代码:
依然使用stuapi/models.py里面的Student作为操作模型。
序列化器,drfview/serializers.py,代码:
from rest_framework import serializers
from stuapi import models
class StudentModelSerializer(serializers.ModelSerializer):
"""学生信息的序列化器"""
class Meta:
model = models.Student
fields = "__all__"
视图代码:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from stuapi import models
from . import serializers
"""
POST /students/ 添加一个学生信息
GET /students/ 获取所有学生信息
GET /students/<pk>/ 获取一个学生信息
PUT /students/<pk>/ 更新一个学生信息
DELETE /students/<pk>/ 删除一个学生信息
一个路由对应一个视图类,所以我们可以把5个API分成2个类来完成
"""
# Create your views here.
class StudentAPIView(APIView):
def get(self, request):
"""获取所有学生信息"""
# 获取数据
student_objs = models.Student.objects.all()
# 序列化,instance参数是QuerySet,必须声明many参数
serializer = serializers.StudentModelSerializer(instance=student_objs, many=True)
# 返回结果
return Response(serializer.data)
def post(self,request):
"""添加一个学生信息"""
# 接受客户端数据,并反序列化【验证、保存】
serializer = serializers.StudentModelSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
# 返回结果
return Response(serializer.data, status=status.HTTP_201_CREATED)
class StudentInfoAPIView(APIView):
def get(self, request, pk):
"""获取一个学生信息"""
# 读取数据
try:
instance = models.Student.objects.get(pk=pk)
except models.Student.DoesNotExist:
return Response({"errors": "当前学生信息不存在!"}, status=status.HTTP_400_BAD_REQUEST)
# 序列化
serializer = serializers.StudentModelSerializer(instance=instance)
# 返回结果
return Response(serializer.data)
def delete(self, request, pk):
"""删除一个学生信息"""
# 删除数据
models.Student.objects.filter(pk=pk).delete()
# 返回结果
return Response({}, status=status.HTTP_204_NO_CONTENT)
def put(self, request, pk):
"""更新一个学生信息"""
# 查询要更新的学生信息
try:
instance = models.Student.objects.get(pk=pk)
except models.Student.DoesNotExist:
return Response({"errors": "当前学生信息不存在!"}, status=status.HTTP_400_BAD_REQUEST)
# 反序列化【校验、保存】,如果允许修改部分字段,则可以设置partial=True
serializer = serializers.StudentModelSerializer(instance=instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
# 返回序列化结果
return Response(serializer.data, status=status.HTTP_201_CREATED)
路由代码:
from django.urls import path,re_path
from . import views
urlpatterns = [
path("student/", views.StudentAPIView.as_view()),
re_path(r"^student/(?P<pk>\d+)/$", views.StudentInfoAPIView.as_view()),
]
2.2.1.2 GenericAPIView[通用视图类]¶
通用视图类在继承了APIView的所有功能以外,还提供了几个属性和方法让我们可以把视图中独特的代码抽取出来作为类属性,让视图方法中的代码变得更加通用,方便把通用代码进行简写。
继承自APIView,主要增加了操作序列化器和数据库查询的方法,作用是为下面Mixin扩展类(混入类)的执行提供方法支持。通常在使用时,可搭配一个或多个Mixin扩展类。
提供的关于序列化器使用的1个属性与2个方法
-
属性:
-
serializer_class=序列化器 指明视图使用的序列化器类
-
方法:
-
getserializerclass(self)
当出现一个视图类中调用多个序列化器时,那么可以通过条件判断在getserializerclass方法中通过返回不同的序列化器类名就可以让视图方法执行不同的序列化器对象了。
返回序列化器类,默认返回
serializer_class,可以重写,例如:class Student2GenericAPIView(GenericAPIView): # 整个视图类只使用一个序列化器的情况 # serializer_class = StudentModelSerializert # 整个视图类中使用多个序列化器的情况 def get_serializer_class(self): if self.request.method.lower() == "put": return StudentModelSerializer else: return Student2ModelSerializer queryset = Student.objects.all() def get(self, request, pk): """获取一个模型信息""" serializer = self.get_serializer(instance=self.get_object()) return Response(serializer.data) def put(self, request, pk): """更新一个模型信息""" serializer = self.get_serializer(instance=self.get_object(), data=request.data) serializer.is_valid(raise_exception=True) serializer.save() return Response(serializer.data) -
get_serializer(self, args, *kwargs)¶
返回序列化器对象,主要用来提供给Mixin扩展类使用,如果我们在视图中想要获取序列化器对象,也可以直接调用此方法。
注意,该方法在提供序列化器对象的时候,会向序列化器对象的context属性补充三个数据:request、format、view,这三个数据对象可以在序列化器内部使用。
- request 当前视图的请求对象
- view 当前请求的类视图对象
- format 当前请求期望返回的数据格式
提供的关于数据库查询的1个属性与2个方法
-
属性:
-
queryset 指明使用的数据查询的结果集
-
方法:
-
get_queryset(self)
返回视图使用的查询集QuerySet,主要用来提供给Mixin扩展类使用,是列表视图与详情视图获取数据的基础,默认返回
queryset属性,可以重写,当获取数据时需要设置一些查询条件,则可以重写如下,例如: -
get_object(self)
返回详情视图所需的1个模型类数据对象,主要用来提供给Mixin扩展类使用。
在试图中可以调用该方法获取详情信息的模型类对象。
若详情访问的模型类对象不存在,会返回404异常。
该方法会默认使用APIView提供的checkobjectpermissions方法检查当前客户端是否有权限访问当前模型对象。
举例:
# 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() # get_object()方法 本质上就是 self.queryset.get(pk=pk) serializer = self.get_serializer(book) # 本质上 self.serializer_class(book) return Response(serializer.data)
其他可以设置的属性
- pagination_class 指明分页控制类
- filter_backends 指明数据过滤控制后端,允许客户端通过地址栏传递过滤参数
视图,代码:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from stuapi import models
from . import serializers
from rest_framework.generics import GenericAPIView
class StudentGenericAPIView(GenericAPIView):
# queryset 设置当前视图类所有的视图方法中数据来源[QuerySet]
queryset = models.Student.objects.all()
# serializer_class 设置当前视图类所有视图方法中使用的公共序列化器
serializer_class = serializers.StudentModelSerializer
def get(self, request):
"""获取所有数据"""
# 获取数据
instance_objs = self.get_queryset()
# 序列化
serializer = self.get_serializer(instance=instance_objs, many=True)
# 返回数据
return Response(serializer.data)
def post(self, request):
"""添加一条数据"""
# 接受客户端数据,并反序列化【验证、保存】
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
# 返回结果
return Response(serializer.data, status=status.HTTP_201_CREATED)
class StudentInfoGenericAPIView(GenericAPIView):
queryset = models.Student.objects
serializer_class = serializers.StudentModelSerializer
def get(self, request, pk):
"""获取一条数据"""
# 读取数据
# instance = models.Student.objects.get(pk=pk)
# instance = self.get_queryset().get(pk=pk) # 上一句代码的简写
instance = self.get_object() # 上一句代码的简写
# 序列化
serializer = self.get_serializer(instance=instance)
# 返回结果
return Response(serializer.data)
def put(self, request, pk):
"""更新一条数据"""
# 获取要更新模型对象
instance = self.get_object()
# 反序列化
# 反序列化【校验、保存】,如果允许修改部分字段,则可以设置partial=True
serializer = self.get_serializer(instance=instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
# 返回结果
return Response(serializer.data)
def delete(self, request, pk):
"""删除一条数据"""
# 删除数据
self.get_object().delete()
# 返回结果
return Response({}, status=status.HTTP_204_NO_CONTENT)
路由代码:
from django.urls import path,re_path
from . import views
urlpatterns = [
# APIView
path("students/", views.StudentAPIView.as_view()),
re_path("^students/(?P<pk>\d+)/$", views.StudentInfoAPIView.as_view()),
# GenericAPIView
path("students1/", views.StudentGenericAPIView.as_view()),
re_path("^students1/(?P<pk>\d+)/$", views.StudentInfoGenericAPIView.as_view()),
]
2.1.2 5个视图扩展类¶
也叫视图混入类,作用:提供了对数据资源进行增删改查的视图方法处理流程的实现,如果需要编写的视图方法属于这五种,则视图可以通过继承相应的扩展类来复用代码,减少自己编写的代码量。
这五个视图扩展类需要搭配GenericAPIView通用视图类,因为五个扩展类的实现需要调用GenericAPIView提供的序列化器与数据库查询的方法。
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)
举例:
"""
视图扩展类必须配合GenericAPIVIew来使用
ListModelMixin -> list 获取多条数据
CreateModelMixin -> create 添加一条数据
RetrieveModelMixin -> retrieve 获取一条数据
UpdateModelMixin -> update 更新一条数据
DestroyModelMixin -> destroy 删除一条数据
"""
from rest_framework import mixins
class StudentMixinAPIView(GenericAPIView, mixins.ListModelMixin, mixins.CreateModelMixin):
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
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 {}
视图代码:
from rest_framework import mixins
class StudentMixinAPIView(GenericAPIView, mixins.ListModelMixin, mixins.CreateModelMixin):
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
def get(self, request):
"""获取所有数据"""
return self.list(request)
def post(self, request):
"""添加一条数据"""
return self.create(request)
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)
视图代码:
from rest_framework import mixins
class StudentInfoMixinAPIView(GenericAPIView, mixins.RetrieveModelMixin):
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
def get(self, request, pk):
"""获取一条数据"""
return self.retrieve(request, pk)
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)
视图代码:
from rest_framework import mixins
class StudentInfoMixinAPIView(GenericAPIView, mixins.RetrieveModelMixin, mixins.UpdateModelMixin):
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
def get(self, request, pk):
"""获取一条数据"""
return self.retrieve(request, pk)
def put(self, request, pk):
"""更新一条数据"""
return self.update(request, pk)
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()
视图代码:
"""
视图扩展类必须配合GenericAPIVIew来使用
ListModelMixin -> list 获取多条数据
CreateModelMixin -> create 添加一条数据
RetrieveModelMixin -> retrieve 获取一条数据
UpdateModelMixin -> update 更新一条数据
DestroyModelMixin -> destroy 删除一条数据
"""
from rest_framework import mixins
class StudentInfoMixinAPIView(GenericAPIView, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin):
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
def get(self, request, pk):
"""获取一条数据"""
return self.retrieve(request, pk)
def put(self, request, pk):
"""更新一条数据"""
return self.update(request, pk)
def delete(self, request, pk):
"""删除一条数据"""
return self.destroy(request, pk)
整体代码,使用GenericAPIView结合视图扩展类,实现5个基本api接口,视图代码:
"""
视图扩展类必须配合GenericAPIVIew来使用
ListModelMixin -> list 获取多条数据
CreateModelMixin -> create 添加一条数据
RetrieveModelMixin -> retrieve 获取一条数据
UpdateModelMixin -> update 更新一条数据
DestroyModelMixin -> destroy 删除一条数据
"""
from rest_framework import mixins
class StudentMixinAPIView(GenericAPIView, mixins.ListModelMixin, mixins.CreateModelMixin):
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
def get(self, request):
"""获取所有数据"""
return self.list(request)
def post(self, request):
"""添加一条数据"""
return self.create(request)
class StudentInfoMixinAPIView(GenericAPIView, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin):
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
def get(self, request, pk):
"""获取一条数据"""
return self.retrieve(request, pk)
def put(self, request, pk):
"""更新一条数据"""
return self.update(request, pk)
def delete(self, request, pk):
"""删除一条数据"""
return self.destroy(request, pk)
路由代码:
from django.urls import path, re_path
from . import views
urlpatterns = [
# APIView
path("student/", views.StudentAPIView.as_view()),
re_path(r"^student/(?P<pk>\d+)/$", views.StudentInfoAPIView.as_view()),
# GenericAPIView
path("student2/", views.StudentGenericAPIView.as_view()),
re_path(r"^student2/(?P<pk>\d+)/$", views.StudentInfoGenericAPIView.as_view()),
# GenericAPIView+mixins
path("student3/", views.StudentMixinAPIView.as_view()),
re_path(r"^student3/(?P<pk>\d+)/$", views.StudentInfoMixinAPIView.as_view()),
]
2.1.3 9个视图子类¶
1)ListAPIView¶
提供了get视图方法,内部调用了模型扩展类的list方法
继承自:GenericAPIView、ListModelMixin
2)CreateAPIView¶
提供了post视图方法,内部调用了模型扩展类的create方法
继承自: GenericAPIView、CreateModelMixin
3)RetrieveAPIView¶
提供了get视图方法,内部调用了模型扩展类的retrieve方法
继承自: GenericAPIView、RetrieveModelMixin
4)DestroyAPIView¶
提供了delete视图方法,内部调用了模型扩展类的destroy方法
继承自:GenericAPIView、DestoryModelMixin
5)UpdateAPIView¶
提供了put和patch视图方法,内部调用了模型扩展类的update和partial_update方法
继承自:GenericAPIView、UpdateModelMixin
6)ListCreateAPIView¶
提供了get和post方法,内部调用了list和create方法
继承自:GenericAPIView、ListModelMixin、CreateModelMixin
7)RetrieveUpdateAPIView¶
提供 get、put、patch方法
继承自: GenericAPIView、RetrieveModelMixin、UpdateModelMixin
8)RetrieveDestroyAPIView¶
提供 get、delete方法
继承自:GenericAPIView、RetrieveModelMixin、DestroyModelMixin
9)RetrieveUpdateDestroyAPIView¶
提供 get、put、patch、delete方法
继承自:GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestroyModelMixin
视图代码:
"""
drf中了为了进一步简写视图代码,让开发者有更多选择的空间,基于GenericAPIView与视图扩展类进行组合,提供了9个视图子类:
GenericAPIView + mixins.ListModelMixin --> ListAPIView -> get 获取所有数据
GenericAPIView + mixins.CreateModelMixin --> CreateAPIView -> post 添加一条数据
GenericAPIView + mixins.RetrieveModelMixin --> RetrieveAPIView -> get 获取一条数据
GenericAPIView + mixins.UpdateModelMixin --> UpdateAPIView -> put/patch 更新一条数据
GenericAPIView + mixins.DestroyModelMixin --> DestroyAPIView -> delete 删除一条数据
GenericAPIView + mixins.ListModelMixin + mixins.CreateModelMixin --> ListCreateAPIView 获取所有数据/添加一条数据
GenericAPIView + mixins.RetrieveModelMixin + mixins.UpdateModelMixin --> RetrieveUpdateAPIView 获取/更新一条数据
GenericAPIView + mixins.RetrieveModelMixin + mixins.DestroyModelMixin --> RetrieveDestroyAPIView 获取/删除一条数据
GenericAPIView + mixins.RetrieveModelMixin + mixins.UpdateModelMixin + mixins.DestroyModelMixin --> RetrieveUpdateDestroyAPIView 获取/更新/删除一条数据
"""
from rest_framework import generics
# class StudentSonAPIView(generics.ListAPIView, generics.CreateAPIView):
class StudentSonAPIView(generics.ListCreateAPIView):
"""
查询所有数据
添加一条数据
"""
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
# class StudentInfoSonAPIView(generics.RetrieveAPIView, generics.UpdateAPIView, generics.DestroyAPIView):
# class StudentInfoSonAPIView(generics.RetrieveUpdateAPIView, generics.DestroyAPIView):
# class StudentInfoSonAPIView(generics.RetrieveDestroyAPIView, generics.UpdateAPIView):
class StudentInfoSonAPIView(generics.RetrieveUpdateDestroyAPIView):
"""
查询一条数据
更新一条数据
删除一条数据
"""
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
路由,代码:
from django.urls import path, re_path
from . import views
urlpatterns = [
# APIView
path("student/", views.StudentAPIView.as_view()),
re_path(r"^student/(?P<pk>\d+)/$", views.StudentInfoAPIView.as_view()),
# GenericAPIView
path("student2/", views.StudentGenericAPIView.as_view()),
re_path(r"^student2/(?P<pk>\d+)/$", views.StudentInfoGenericAPIView.as_view()),
# GenericAPIView+mixins
path("student3/", views.StudentMixinAPIView.as_view()),
re_path(r"^student3/(?P<pk>\d+)/$", views.StudentInfoMixinAPIView.as_view()),
# 视图子类
path("student4/", views.StudentSonAPIView.as_view()),
re_path(r"^student4/(?P<pk>\d+)/$", views.StudentInfoSonAPIView.as_view()),
]
练习题:
创建一个商品模型,基于视图子类,完成5个API接口。
2.1.4 解决代码重复问题¶
在前面的学习中,我们已经基于视图子类,做到最简化的实现5个API接口了,但是还是存在了2行代码重复的情况。 当然,我们之所以把5个API接口分2个类进行声明,主要原因是: 1. drf提供的视图类,默认使用as_view来进行分发的,依靠是视图方法名与HTTP请求方法进行比对来执行。 获取所有数据的视图方法,与获取一条数据的视图方法,都使用了get请求,所以会同一个类下,不能出现2个get,否则覆盖。 2. 5个API接口中,部分接口不需要pk值,部分接口需要pk值,这就需要声明2次路由,因此如果写在一个类中,也会因为部分视图方法不接收pk而报错
views.py,代码:
class StudentBaseView(object):
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
class StudentView(StudentBaseView, generics.ListCreateAPIView):
"""
查询所有数据 get
添加一条数据
"""
class StudentInfoView(StudentBaseView, generics.RetrieveUpdateDestroyAPIView):
"""
查询一条数据 get
更新一条数据
删除一条数据
"""
urls.py,代码:
from django.urls import path, re_path
from . import views
urlpatterns = [
# APIView
path("student/", views.StudentAPIView.as_view()),
re_path(r"^student/(?P<pk>\d+)/$", views.StudentInfoAPIView.as_view()),
# GenericAPIView
path("student2/", views.StudentGenericAPIView.as_view()),
re_path(r"^student2/(?P<pk>\d+)/$", views.StudentInfoGenericAPIView.as_view()),
# GenericAPIView+mixins
path("student3/", views.StudentMixinAPIView.as_view()),
re_path(r"^student3/(?P<pk>\d+)/$", views.StudentInfoMixinAPIView.as_view()),
# 视图子类
path("student4/", views.StudentSonAPIView.as_view()),
re_path(r"^student4/(?P<pk>\d+)/$", views.StudentInfoSonAPIView.as_view()),
# 实现代码再一次简化
path("student5/", views.StudentView.as_view()),
re_path(r"^student5/(?P<pk>\d+)/$", views.StudentInfoView.as_view()),
]
虽然上面已经通过把重复代码写在父类的方式,解决了代码复用问题,但是依然没有解决必须依靠2个类才能实现5个API接口的情况。所以drf中提供了视图集与路由集给开发者解决了这个问题,让5个API接口甚至更多的API接口方法写在一个视图类下。
2.2 视图集 ViewSet¶
使用视图集ViewSet,可以将一系列视图相关的代码逻辑和相关的http请求动作封装到一个类中。ViewSet视图集类不再限制必须使用http请求(get/post...等)作为视图方法名了,而是实现允许开发者自定义视图方法名,例如 list() 或get_all()、create() 等,例如可以采用以下方法名(action),来代替原有的各种视图方法名:
- list() 获取所有数据
- retrieve() 获取一条数据
- create() 创建一条数据
- update() 更新一条数据
- destory() 删除一条数据
视图集在使用as_view()方法时,设置字典参数允许我们将代表视图方法名与具体http请求进行绑定。
1)ViewSet¶
ViewSet(基本视图集)继承自APIView与ViewSetMixin,作用也与APIView基本类似,用于给开发者编写视图的,也提供了身份认证、权限校验、流量管理等。
ViewSet主要通过继承ViewSetMixin来重写了APIView的asview()方法,允许开发者在调用asview时传递字典参数。
该字典参数的作用就是把视图方法名与HTTP请求动作进行关联映射。如:
path("url路径", 视图类.as_view()) # APIVIew提供的as_view写法
path("url路径", 视图类.as_view({ # ViewSetMixin提供的as_view的写法
# "http请求方法小写": "视图类方法名",
"get": "list",
"put": "update",
}))
在ViewSet中,默认是没有提供任何视图方法的(注意:在视图集中,视图方法名叫action[动作的意思]),需要我们自己实现action方法。
"""视图集 ViewSet实现5个API接口"""
from rest_framework.viewsets import ViewSet
class StudentViewSet(ViewSet):
def get_all(self, request):
"""获取所有数据"""
# 获取数据
instance = models.Student.objects.all()
# 序列化
serializer = serializers.StudentModelSerializer(instance, many=True)
# 返回结果
return Response(serializer.data)
def get_one(self, request, pk):
"""获取一条数据"""
# 获取数据
try:
instance = models.Student.objects.get(pk=pk)
except models.Student.DoesNotExist:
return Response({"errors": "not found"}, status=status.HTTP_404_NOT_FOUND)
# 序列化
serializer = serializers.StudentModelSerializer(instance)
# 返回结果
return Response(serializer.data)
def add(self, request):
"""添加一条数据"""
# 接受数据,反序列化
serializer = serializers.StudentModelSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
# 返回结果
return Response(serializer.data, status=status.HTTP_201_CREATED)
def put(self, request, pk):
"""更新一条数据"""
# 查询数据
try:
instance = models.Student.objects.get(pk=pk)
except models.Student.DoesNotExist:
return Response({"errors": "not found"}, status=status.HTTP_404_NOT_FOUND)
# 接受数据,反序列化
serializer = serializers.StudentModelSerializer(instance, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
# 返回结果
return Response(serializer.data, status=status.HTTP_201_CREATED)
def delete(self, request, pk):
"""删除一条数据"""
# 查询并删除数据
try:
models.Student.objects.get(pk=pk).delete()
except models.Student.DoesNotExist:
return Response({"errors": "not found"}, status=status.HTTP_404_NOT_FOUND)
# 返回结果
return Response(status=status.HTTP_204_NO_CONTENT)
def top_5(self, request):
"""获取年龄最大的5个学生"""
# 查询数据
instance = models.Student.objects.all().order_by("-age")[:5]
# 序列化器
serializer = serializers.StudentModelSerializer(instance, many=True)
# 返回结果
return Response(serializer.data)
在设置路由时,我们可以如下操作
from django.urls import path, re_path
from . import views
urlpatterns = [
# 其他路由代码省略。。。
# ....
# 视图集
path("student6/", views.StudentViewSet.as_view({
# "http请求方法小写": "视图方法名",
"get": "get_all",
"post": "add",
})),
re_path(r"^student6/(?P<pk>\d+)/$", views.StudentViewSet.as_view({
"get": "get_one",
"put": "put",
"delete": "delete",
})),
re_path("student6/top/", views.StudentViewSet.as_view({
"get": "top_5",
}))
]
2)GenericViewSet¶
GenericViewSet(通用视图集)继承自GenericAPIView和ViewSetMixin,作用让视图集的视图代码变得更加通用,抽离独特代码作为视图类的属性。
注意:
GenericViewSet不是ViewSet的子类,它两都是ViewSetMixin的子类。
使用ViewSet通常并不方便,因为视图中所有的功能代码,甚至包括增删改查都要开发者自己编写,而增删改查这些基本方法在学习的Mixin视图扩展类已经提供了,所以我们可以通过使用GenericViewSet与Mixin视图扩展类来简写代码。
视图代码:
"""
GenericViewSet就是GenericAPIView的子类,所以使用GenericViewSet时,也必须声明2个属性:
queryset
serializer_class
"""
from rest_framework.viewsets import GenericViewSet
class StudentGenericViewSet(GenericViewSet):
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
def list(self, request):
"""获取所有数据"""
# 查询数据
instance = self.get_queryset()
# 序列化
serializer = self.get_serializer(instance, many=True)
# 返回结果
return Response(serializer.data)
def create(self, request):
"""添加一条数据"""
# 接受数据,反序列化
serializer = serializers.StudentModelSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
# 返回结果
return Response(serializer.data, status=status.HTTP_201_CREATED)
def retrieve(self, request, pk):
"""获取一条数据"""
# 获取数据
instance = self.get_object()
# 序列化
serializer = serializers.StudentModelSerializer(instance)
# 返回结果
return Response(serializer.data)
def update(self, request, pk):
"""更新一条数据"""
# 查询数据,获取数据,反序列化
serializer = serializers.StudentModelSerializer(instance=self.get_object(), data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
# 返回结果
return Response(serializer.data, status=status.HTTP_201_CREATED)
def destroy(self, request, pk):
"""删除一条数据"""
self.get_object().delete()
return Response(status=status.HTTP_204_NO_CONTENT)
def top_5(self, request):
"""获取年龄最大的5个学生"""
# 查询数据
instance = self.get_queryset().order_by("-age")[:5]
# 序列化器
serializer = self.get_serializer(instance, many=True)
# 返回结果
return Response(serializer.data)
路由代码:
from django.urls import path, re_path
from . import views
urlpatterns = [
# 前面的路由代码省略...
# ....
# GenericViewSet通用视图集
path("student7/", views.StudentGenericViewSet.as_view({
"get": "list",
"post": "create",
})),
re_path(r"^student7/(?P<pk>\d+)/$", views.StudentGenericViewSet.as_view({
"get": "retrieve",
"put": "update",
"delete": "destroy",
})),
re_path("student7/top/", views.StudentGenericViewSet.as_view({
"get": "top_5",
})),
]
GenericViewSet+Mixins¶
结合我们上面学习的Mixins视图扩展类,实现基本API接口的代码简写操作,视图代码:
"""
通用视图集让我们编写的基本API接口,变得通用了,
所以接下来,可以使用视图扩展类+通用视图集来简写代码了
"""
from rest_framework.viewsets import GenericViewSet
from rest_framework import mixins
class StudentMixinViewSet(
GenericViewSet,
mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin
):
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
路由,代码:
from django.urls import path, re_path
from . import views
urlpatterns = [
# 前面的路由代码省略...
# ....
# GenericViewSet通用视图集 + Mixins视图扩展类
path("student8/", views.StudentMixinViewSet.as_view({
"get": "list",
"post": "create",
})),
re_path(r"^student8/(?P<pk>\d+)/$", views.StudentMixinViewSet.as_view({
"get": "retrieve",
"put": "update",
"delete": "destroy",
})),
]
3)ModelViewSet¶
ModelViewSet(模型视图集),继承自GenericViewSet,同时还继承了5个视图扩展类(ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin)。
视图代码:
"""
上面的代码中,基于视图扩展类+通用视图集实现的API接口如果同时存在5个基本功能,可以使用ModelViewSet直接代替
"""
from rest_framework.viewsets import ModelViewSet
class StudentModelViewSet(ModelViewSet):
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
路由代码:
from django.urls import path, re_path
from . import views
urlpatterns = [
# 前面的路由代码省略...
# ....
# ModelViewSet
path("student9/", views.StudentModelViewSet.as_view({
"get": "list",
"post": "create",
})),
re_path(r"^student9/(?P<pk>\d+)/$", views.StudentModelViewSet.as_view({
"get": "retrieve",
"put": "update",
"delete": "destroy",
})),
]
4)ReadOnlyModelViewSet¶
ReadOnlyModelViewSet(只读模型视图集),继承自GenericViewSet,同时包括了2个视图扩展类(ListModelMixin、RetrieveModelMixin)。
视图代码:
"""
开发者中是否涉及到权限的问题,一部分人可能只允许查看数据,不允许添加/修改/删除的情况,我们可以基于只读模型视图集来完成API接口的快速编写。
ReadOnlyModelViewSet = RetrieveModelMixin + ListModelMixin + GenericViewSet
"""
from rest_framework.viewsets import ReadOnlyModelViewSet
class StudentROModelViewSet(ReadOnlyModelViewSet):
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
路由代码:
from django.urls import path, re_path
from . import views
urlpatterns = [
# 前面的路由代码省略...
# ....
# ReadOnlyModelViewSet
path("student10/", views.StudentROModelViewSet.as_view({
"get": "list",
})),
re_path(r"^student10/(?P<pk>\d+)/$", views.StudentROModelViewSet.as_view({
"get": "retrieve",
})),
]
3. 路由集Routers¶
对于视图集ViewSet,我们除了可以自己手动指明请求方式与视图方法之间的对应关系外,还可以使用Routers来帮助我们快速实现路由信息。如果是非视图集,不需要使用路由集routers。
REST framework提供了两个router类,使用方式一致的。结果多一个或少一个根目录url地址的问题而已。
- SimpleRouter 线上运营项目
- DefaultRouter 本地开发,项目上线前
2)DefaultRouter
DefaultRouter与SimpleRouter的区别是,DefaultRouter会多附带一个默认的API根视图,返回一个包含所有列表视图的超链接响应数据。
3.1 使用方法¶
1) 创建router对象,并注册视图集。
demo/urls.py,代码:
from django.urls import path, re_path
from . import views
from rest_framework import routers
"""使用路由集注册生成视图集的路由信息"""
simple_router = routers.SimpleRouter()
simple_router.register(prefix="student11", viewset=views.StudentModelViewSet, basename="student11")
default_router = routers.DefaultRouter()
default_router.register(prefix="student12", viewset=views.StudentModelViewSet, basename="student12")
urlpatterns = [
# 中间声明的其他的路由信息
] + simple_router.urls + default_router.urls
print(" simple_router.urls=", simple_router.urls)
print("urlpatterns=", urlpatterns)
print('simple_router=', default_router.urls)
print("default_router.urls=", default_router.urls)
print("urlpatterns=", urlpatterns)
print('default_router=', default_router.urls)
register(prefix, viewset, basename)
- prefix 该视图集的路由前缀
- viewset 视图集
- basename 路由别名的前缀
如上述代码会形成的路由如下:
url: ^students9/$ basename: students9-list
url: ^students9/(?P<pk>[^/.]+)/$ basename: students9-detail
2)把路由对象生成的视图集的路由列表添加到django的路由中可以有两种方式:
或
3.2 action装饰器自动根据视图自定义方法生成路由¶
在视图集中,如果想要让Router自动帮助我们为自定义视图方法生成对应路由信息,需要使用rest_framework.decorators.action装饰器。action装饰器可以让开发者在视图中绑定要路由集生成的url地址
action装饰器可以接收两个参数:
-
methods: 声明被action装饰的视图方法允许外界通过哪些HTTP请求访问,默认是get。
-
detail: 声明被action装饰的视图方法名生成自定义路径,是否使用当前类作为路径的尾缀。
- detail=True 表示将来生成url格式是
<prefix>/<pk>/<url_path>/ -
detail=False 表示将来生成url格式是
<prefix>/<url_path>/ -
url_path:声明该action的路由尾缀。默认就是视图方法名
举例:
from rest_framework.viewsets import ModelViewSet
from rest_framework import decorators
class StudentModelViewSet(ModelViewSet):
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
@decorators.action(methods=["GET"], detail=False) # 用于告诉路由集该如何生成url路径的,是否要加上pk值等等。
def top_5(self, request):
"""获取所有学生呢个中年龄醉的5个学生"""
# 视图集中存在一个自定义方方法时,可以快速基本的API接口,然后在代码后面补充新的方法
# 查询数据
instance = self.get_queryset().order_by("-age")[:5]
# 序列化器
serializer = self.get_serializer(instance, many=True)
# 返回结果
return Response(serializer.data)
urls.py,代码:
from django.urls import path, re_path
from . import views
"""使用路由集注册生成视图集的路由信息"""
from rest_framework import routers
# simple_router = routers.SimpleRouter()
# simple_router.register(prefix="student11", viewset=views.StudentModelViewSet, basename="student11")
default_router = routers.DefaultRouter()
default_router.register(prefix="student12", viewset=views.StudentModelViewSet, basename="student12")
urlpatterns = [
] + default_router.urls
# print(" simple_router.urls=", simple_router.urls)
# print("urlpatterns=", urlpatterns)
# print('simple_router=', default_router.urls)
print("default_router.urls=", default_router.urls)
print("urlpatterns=", urlpatterns)
print('default_router=', default_router.urls)
由路由器自动为此视图集自定义action方法形成的路由会是如下内容:


