drf序列化器之请求、响应以及视图

1. http请求处理

drf除了在数据序列化部分简写代码以外,还在视图中提供了简写操作。所以在django原有的django.views.View类基础上,drf封装了多个视图子类出来提供给我们使用。

Django REST framwork 提供的视图的主要作用:

  • 控制序列化器的执行(检验、保存、转换数据)
  • 控制数据库查询的执行
  • 调用请求类和响应类[这两个类也是由drf帮我们再次扩展了一些功能类。

为了方便我们学习,所以先创建一个子应用req

python manage.py startapp req

注册子应用:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # 注册 rest_framework以及子应用req
    'rest_framework',
    'req',     # 请求与响应
]

注册路由

# 子应用req路由
from django.urls import path
from . import views
urlpatterns = [
    path("students1/",views.StutentView.as_view()),
]


# 总路由defdemo/urls.py
from django.contrib import admin
from django.urls import path,include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('req/', include("req.urls")),
]

1.1. 请求与响应

我们先看一下django的request和response

from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
from django.views import View

# Create your views here.
# django视图中的请求和响应
class StutentView(View):
    def get(self,request):
        print(request)  # <WSGIRequest: GET '/req/students1/'>,WSGIRequest的实例对象,WSGIRequest是HttpRequest的子类
        response = HttpResponse("ok")
        print(response)    # <HttpResponse status_code=200, "text/html; charset=utf-8">
        return response

postman测试

浏览器:

1.2 drf的视图

路由:

# req子路由
from django.urls import path
from . import views
urlpatterns = [
    path("students1/",views.StutentView.as_view()),
    path("students2/",views.StudentAPIView.as_view()),
]

# 总路由drfdemo/urls.py
from django.contrib import admin
from django.urls import path,include

# 2.x之后django把url拆分成了两个路由函数
# django.urls.path 专门编写字符串路由
# django.urls.re_path 专门编写正则路由
urlpatterns = [
    path('admin/', admin.site.urls),
    path('req/', include("req.urls")),
]

视图

from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
from django.views import View

# Create your views here.
# django视图中的请求和响应
class StutentView(View):
    def get(self,request):
        print(request)  # <WSGIRequest: GET '/req/students1/'>
        response = HttpResponse("ok")
        print(response)    # <HttpResponse status_code=200, "text/html; charset=utf-8">
        return response


from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework import status
class StudentAPIView(APIView):
    # 继承的视图类决定了方法中的request对象到底是那个请求对象。
    def get(self,request):
        print(request)    # <rest_framework.request.Request object at 0x7f17d5b07080>
        # rest_framework.request.Request是drf自己独立声明的,不是django的子类
        # drf的request对象中有一个属性叫 _request,这个属性对应的就是django的HttpRequest对象

        response = Response("ok")
        # rest_framework.response.Response
        # drf的response对象就是django的HttpResponse的子类
        print(response)
        return response

postman:

浏览器:

小结:

"""
在django中,我们可以在视图里面使用request和response接收和返回数据给客户端,
主要是因为当前视图类继承了django.views.View的原因

在drf中,我们可以在视图里面使用request和response接收和返回数据给客户端,
这里主要是因为视图类集成了rest_framework.views.APIView,所有有好看的界面。
"""

1.3 常用属性

1).data

request.data 返回解析之后的请求体数据。类似于Django中标准的request.POSTrequest.FILES属性,但提供如下特性:

  • 包含了解析之后的文件和非文件数据
  • 包含了对POST、PUT、PATCH请求方式解析后的数据
  • 利用了REST framework的parsers解析器,不仅支持表单类型数据,也支持JSON数据

2).query_params

request.query_params返回解析之后的查询字符串数据(http://127.0.0.1:8000/req/students2/?name="oldboy"),就是查询地址?后面一堆k,v

request.query_params与Django标准的request.GET相同,只是更换了更正确的名称而已。

示例:

路由:

# req子路由
from django.urls import path
from . import views
urlpatterns = [
    path("students1/",views.StutentView.as_view()),
    path("students2/",views.StudentAPIView.as_view()),
]

# 总路由drfdemo/urls.py
from django.contrib import admin
from django.urls import path,include

# 2.x之后django把url拆分成了两个路由函数
# django.urls.path 专门编写字符串路由
# django.urls.re_path 专门编写正则路由
urlpatterns = [
    path('admin/', admin.site.urls),
    path('req/', include("req.urls")),
]

视图:

from django.shortcuts import render, HttpResponse
from django.http import JsonResponse
from django.views import View


# Create your views here.
# django视图中的请求和响应
class StutentView(View):
    def get(self, request):
        print(request)  # <WSGIRequest: GET '/req/students1/'>,WSGIRequest的实例对象,WSGIRequest是HttpRequest的子类
        response = HttpResponse("ok")
        print(response)  # <HttpResponse status_code=200, "text/html; charset=utf-8">
        return response


from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework import status
class StudentAPIView(APIView):
    # 继承的视图类决定了方法中的request对象到底是那个请求对象。
    def get(self,request):
        response = Response("ok")
        return response

    def post(self, request):
        print(request.data.dict())  # 接收http请求体数据
        """
        打印效果
        如果客户端上传的是json:
        {'title': '老人与海', 'pub_date': '2022-3-30', 'read': 1, 'comment': 0, 'price': -17}

        如果客户端上传的是form表单:
        <QueryDict: {'title': ['python']}>
        如果QuerySet对象想转换成传统字典使用request.data.dict()就会变成:{'title': 'python'}

        """
        response = Response("ok")
        return response

postman测试:

传json

传表单

1.4 Response

from rest_framework.response import Response

REST framework提供了一个响应类Response,使用该类构造响应对象时,响应的具体数据内容会被转换(render渲染器)成符合前端需求的类型。

REST framework提供了Renderer 渲染器,用来根据请求头中的Accept(接收数据类型声明)来自动转换响应数据到对应格式。如果前端请求中未进行Accept声明,则会采用默认方式处理响应数据,我们可以通过配置来修改默认响应格式。【简而言之,就是Renderer能通过请求找的Accept查询出客户端支持和希望的数据类型,把视图的结果以客户端能识别的格式返回】

可以在rest_framework.settings.py查找所有的drf默认配置项

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': (  # 默认响应渲染类
        'rest_framework.renderers.JSONRenderer',  # json渲染器
        'rest_framework.renderers.BrowsableAPIRenderer',  # 浏览器API渲染器
    )
}

1.4.1 构造方式

Response(data, status=None, template_name=None, headers=None, exception=False, content_type=None)

data数据不要是render处理之后的数据,只需传递python的内建类型数据即可,REST framework会使用renderer渲染器处理data

data不能是复杂结构的数据,如Django的模型类对象,对于这样的数据我们可以使用Serializer序列化器序列化处理后(转为了Python字典类型)再传递给data参数。

参数说明:

  • data: 为响应准备的序列化处理后的数据;serializer.data、或者{“message”:”ok”}
  • status: 状态码,默认200;
  • template_name: 模板名称,如果使用HTMLRenderer 时需指明;
  • headers: 用于存放响应头信息的字典;例如可以放token
  • content_type: 响应数据的Content-Type,通常此参数无需传递,REST framework会根据前端所需类型数据来设置该参数。

1.4.2 常用属性

1).data

传给response对象的序列化后,但尚未render处理的数据

2).status_code

状态码的数字

3).content

经过render处理后的响应数据

1.4.3 状态码

为了方便设置状态码,REST framewrok在rest_framework.status模块中提供了常用状态码常量。

from rest_framework import status

1)信息告知 - 1xx

HTTP_100_CONTINUE
HTTP_101_SWITCHING_PROTOCOLS

2)成功 - 2xx

HTTP_200_OK   # 常规请求
HTTP_201_CREATED  # 创建成功
HTTP_202_ACCEPTED
HTTP_203_NON_AUTHORITATIVE_INFORMATION
HTTP_204_NO_CONTENT
HTTP_205_RESET_CONTENT
HTTP_206_PARTIAL_CONTENT
HTTP_207_MULTI_STATUS

3)重定向 - 3xx

HTTP_300_MULTIPLE_CHOICES
HTTP_301_MOVED_PERMANENTLY   # 重定向是永久的重定向,搜索引擎在抓取新内容的同时也将旧的网址替换为重定向之后的网址
HTTP_302_FOUND   # 跳转是暂时的跳转,搜索引擎会抓取新的内容而保留旧的网址。因为服务器返回302代码,搜索引擎认为新的网址只是暂时的
HTTP_303_SEE_OTHER
HTTP_304_NOT_MODIFIED
HTTP_305_USE_PROXY
HTTP_306_RESERVED
HTTP_307_TEMPORARY_REDIRECT

4)客户端错误 - 4xx

HTTP_400_BAD_REQUEST
HTTP_401_UNAUTHORIZED
HTTP_402_PAYMENT_REQUIRED
HTTP_403_FORBIDDEN     # 服务器拒绝访问,很可能是没有权限
HTTP_404_NOT_FOUND     # 无法找到文件
HTTP_405_METHOD_NOT_ALLOWED    # 请求方法不存在
HTTP_406_NOT_ACCEPTABLE
HTTP_407_PROXY_AUTHENTICATION_REQUIRED
HTTP_408_REQUEST_TIMEOUT
HTTP_409_CONFLICT
HTTP_410_GONE
HTTP_411_LENGTH_REQUIRED
HTTP_412_PRECONDITION_FAILED
HTTP_413_REQUEST_ENTITY_TOO_LARGE
HTTP_414_REQUEST_URI_TOO_LONG
HTTP_415_UNSUPPORTED_MEDIA_TYPE
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE
HTTP_417_EXPECTATION_FAILED
HTTP_422_UNPROCESSABLE_ENTITY
HTTP_423_LOCKED
HTTP_424_FAILED_DEPENDENCY
HTTP_428_PRECONDITION_REQUIRED
HTTP_429_TOO_MANY_REQUESTS
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE
HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS

5)服务器错误 - 5xx

HTTP_500_INTERNAL_SERVER_ERROR   # 服务端异常
HTTP_501_NOT_IMPLEMENTED
HTTP_502_BAD_GATEWAY
HTTP_503_SERVICE_UNAVAILABLE
HTTP_504_GATEWAY_TIMEOUT
HTTP_505_HTTP_VERSION_NOT_SUPPORTED
HTTP_507_INSUFFICIENT_STORAGE
HTTP_511_NETWORK_AUTHENTICATION_REQUIRED

1.5 Request和Reponse视图中使用案例

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework import status
class StudentAPIView(APIView):
    # 继承的视图类决定了方法中的request对象到底是那个请求对象。这里是APIView所以request是drf中的
    def get(self,request):
        print(request)
        # rest_framework.request.Request是drf自己独立声明的,不适django的子类
        # drf的request对象中有一个属性叫 _request,这个属性对应的就是django的HttpRequest对象

        response = Response("ok")
        # rest_framework.response.Response
        # drf的response对象就是django的HttpResponse的子类
        print(response)
        return response

    def post(self,request):
        """请求对象的学习"""
        print(request.data)  # 接受http请求体数据,目前默认的配置中只能接受表单和json数据,
        # 其他格式的数据不能request.data来获取
        """打印效果:
        如果客户端上传的是json:
        {'title': '西游记', 'price': 20, 'pub_date': '2020-10-10', 'read': 200, 'comment': 20}
        如果客户端上传的是表单
        <QueryDict: {'title': ['老男孩']}>
        """
        print(request.query_params)  # 这里其实本质上就是django.http.request.HttpRequest对象的GET属性

        """
        请求: /req/students2/?name=xiaoming
        打印效果:
        <QueryDict: {'name': ['xiaoming']}>
        """
        return Response(data="ok", status=status.HTTP_201_CREATED, headers={"company": "laonanhai"})

1.6 自定义封装Response

1、路由

# req子路由
from django.urls import path
from . import views
urlpatterns = [
    path("students1/",views.StutentView.as_view()),  # 测试自定义封装Response
]

# 总路由drfdemo/urls.py
from django.contrib import admin
from django.urls import path,include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('req/', include("req.urls")),
]

2、在子应用下新建utils.py

class MyResponse():
    def __init__(self):
        self.status=100
        self.msg='成功'
    @property
    def get_dict(self):
        return self.__dict__

if __name__ == '__main__':
    res=MyResponse()
    res.status=101
    res.msg='查询失败'
    # res.data={'name':'lqz'}
    print(res.get_dict)

3、在views.py中使用

from django.http import JsonResponse
from django.views import View
from .utils import MyResponse


# Create your views here.
# django视图中的请求和响应
class StutentView(View):
    def get(self, request):
        res = MyResponse()
        res.status = 101
        res.msg = '查询失败'
        res.data = {'name': 'lqz'}
        print(res.get_dict)
        return JsonResponse(res.get_dict)

postman测试

1.7 通过继承Response,重新封装MyResponse

myresponse.py

from rest_framework.response import Response


class MyResponse(Response):
    def __init__(self, status=0, msg='ok', headers=None, exception=False, content_type=None,
                 **kwargs):
        data = {
            'status': status,
            'msg': msg
        }
        # 在外界数据可以用result和results来存储
        if kwargs:
            data.update(kwargs)
        super().__init__(data=data, headers=headers, exception=exception, content_type=content_type)

views.py

from django.contrib import admin
from django.urls import path,re_path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('books/', views.BookView.as_view()),
]

执行效果

1.8 全局和局部配置响应

浏览器响应成浏览器的格式,postman响应成json格式,通过配置实现的(默认配置)

#不管是postman还是浏览器,都返回json格式数据
# drf有默认的配置文件---》先从项目的setting中找,找不到,采用默认的
# drf的配置信息,先从自己类中找--》项目的setting中找---》默认的找
    -局部使用:对某个视图类有效
        -在视图类中写如下
        from rest_framework.renderers import JSONRenderer
        renderer_classes=[JSONRenderer,]
    -全局使用:全局的视图类,所有请求,都有效
        -在setting.py中加入如下,REST_FRAMEWORK变量中都是drf的配置信息
        REST_FRAMEWORK = {
            'DEFAULT_RENDERER_CLASSES': (  # 默认响应渲染类
                'rest_framework.renderers.JSONRenderer',  # json渲染器
                'rest_framework.renderers.BrowsableAPIRenderer',  # 浏览API渲染器
            )
        }

局部使用示例:

views.py

from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.renderers import JSONRenderer   # 要启用的配置先导入


# Create your views here.
class TestView(APIView):
    renderer_classes = [JSONRenderer, ]   # 重写APIView中renderer_classes,只对TestView视图类有效
    def get(self, request):
        print(request)

        return Response({"name": "alias"}, status=201, headers={"token": "xxx"})

2. 视图

2.1 drf提供的视图主要作用

Django REST framwork 提供的视图的主要作用:

  • 控制序列化器的执行(检验、保存、转换数据)
  • 控制数据库查询的执行[数据库的删除/查询代码写在视图中,更新和添加写在序列化器]

2.2 视图

REST framework 提供了众多的通用视图基类与扩展类,以简化视图的编写。

drf视图的四大核心:APIView、GenericAPIView、视图集和视图扩展类

2.2.1 2个视图基类

2.2.2 APIView

from rest_framework.views import APIView

APIView是REST framework提供的所有视图的基类,继承自Django的View父类。

drf的APIView与django的View的不同之处在于:

  • 传入到视图方法中的是REST framework的Request对象,而不是Django的HttpRequeset对象;
  • 视图方法可以返回REST framework的Response对象,视图会为响应数据设置(render)符合前端要求的格式;
  • 任何APIException异常都会被捕获到,并且处理成合适的响应信息;
  • 重写了as_view(),在进行dispatch()路由分发前,会对http请求进行身份认证、权限检查、访问流量控制。

支持定义的类属性

  • authentication_classes 列表或元组,身份认证类
  • permissoin_classes 列表或元组,权限检查类
  • throttle_classes 列表或元祖,流量控制类

举例:在APIView中仍以常规的类视图定义方法来实现get() 、post() 或者其他请求方式的方法。

子应用demo/views.py

# Create your views here.
"""APIView是drf里面提供的所有视图类的父类
   APIView提供的功能/属性/方法是最少的,所以使用APIView基本类似我们使用django的View
"""
"""
GET   /students/ 获取多个学生信息 
POST  /students/ 添加一个学生信息

GET    /students/<pk>/  获取一个学生信息 
PUT    /students/<pk>/  修改一个学生信息
DELETE /students/<pk>/  删除一个学生信息
"""
from rest_framework.views import APIView
from students.models import Student
from .serializers import StudentModelSerializer
from rest_framework.response import Response
from rest_framework import status

class StudentAPIView(APIView):
    def get(self,request):
        # 1. 获取学生信息的数据模型
        student_list = Student.objects.all()
        # 2. 调用序列化器
        serializer = StudentModelSerializer(instance=student_list, many=True)
        # 3. 返回数据
        return Response(serializer.data)

    def post(self,request):
        # 1. 调用序列化器对用户提交的数据进行验证
        serializer = StudentModelSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        # 2. 调用序列化器进行数据库操作
        instance = serializer.save() # save()方法返回的是添加成功以后的模型对象

        serializer = StudentModelSerializer(instance=instance)

        # 3. 返回新增数据
        return Response(serializer.data, status=status.HTTP_201_CREATED)


class Student2APIView(APIView):
    def get(self,request,pk):
        # 1. 根据pk获取模型对象
        student = Student.objects.get(pk=pk)
        # 2. 序列化器转换数据
        serializer = StudentModelSerializer(instance=student)
        # 3. 响应数据
        return Response(serializer.data)

    def put(self,request,pk):
        # 1. 通过pk查询学生信息
        student = Student.objects.get(pk=pk)

        # 3. 调用序列化器对客户端发送过来的数据进行验证
        serializer = StudentModelSerializer(instance=student, data=request.data)
        serializer.is_valid(raise_exception=True)
        # 4. 保存数据
        instance = serializer.save()

        serializer = StudentModelSerializer(instance=instance)

        # 5. 返回结果
        return Response(serializer.data, status=status.HTTP_201_CREATED)

    def delete(self, request, pk):
        # 1. 通过pk查询学生信息
        Student.objects.get(pk=pk).delete()
        return Response({"message":"ok"}, status=status.HTTP_204_NO_CONTENT)

子应用demo/serializers.py

from rest_framework import serializers
from students.models import Student
class StudentModelSerializer(serializers.ModelSerializer):
    """学生序列化器"""
    class Meta:
        model = Student
        fields = "__all__"

总路由defdemo/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('demo/', include("demo.urls")),
]

demo/urls.py

from django.urls import path,re_path
from . import views
urlpatterns = [
    path("students1/",views.Student1APIView.as_view()),
    re_path("students2/(?P<pk>\d+)/", views.Student2APIView.as_view()),
]

2.2.3 GenericAPIView

通用视图类主要作用就是把视图中的独特的代码抽取出来,让视图方法中的代码更加通用,方便把通用代码进行简写。

from rest_framework.generics import GenericAPIView

继承自APIVIew主要增加了操作序列化器和数据库查询的方法,作用是为下面Mixin扩展类的执行提供方法支持。通常在使用时,可搭配一个或多个Mixin扩展类。

提供的关于序列化器使用的属性与方法

  • 属性:

    • serializer_class 指明视图使用的序列化器
  • 方法:

    • get_serializer_class(self)

      当出现一个视图类中调用多个序列化器时,那么可以通过条件判断在get_serializer_class方法中通过返回不同的序列化器类名就可以让视图方法执行不同的序列化器对象了。

      返回序列化器类,默认返回serializer_class,可以重写,例如:

      def get_serializer_class(self):
          if self.request.user.is_staff:
              return FullAccountSerializer
          return BasicAccountSerializer
    • get_serializer(self, args, *kwargs)

      返回序列化器对象,主要用来提供给Mixin扩展类使用,如果我们在视图中想要获取序列化器对象,也可以直接调用此方法。

      注意,该方法在提供序列化器对象的时候,会向序列化器对象的context属性补充三个数据:request、format、view,这三个数据对象可以在定义序列化器时使用。

      • request 当前视图的请求对象
      • view 当前请求的类视图对象
      • format 当前请求期望返回的数据格式

提供的关于数据库查询的属性与方法

  • 属性:

    • queryset 指明使用的数据查询集,传queryset对象
      • 例如:queryset = Book.objects.all()
  • 方法:

    • get_queryset(self)

      返回视图使用的查询集(queryset对象),主要用来提供给Mixin扩展类使用,是列表视图与详情视图获取数据的基础,默认返回queryset属性,可以重写,例如:

      def get_queryset(self):
          user = self.request.user
          return user.accounts.all()
    • get_object(self)

      返回详情视图所需的模型类数据对象,主要用来提供给Mixin扩展类使用。

      在试图中可以调用该方法获取详情信息的模型类对象。

      若详情访问的模型类对象不存在,会返回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() # get_object()方法根据pk参数查找queryset中的数据对象
        serializer = self.get_serializer(book)
        return Response(serializer.data)

其他可以设置的属性

  • pagination_class 指明分页控制类
  • filter_backends 指明过滤控制后端

get_object()源码分析,为什么get_object()所在视图对应的url必须是有名分组,而且传到视图的关键字必须是pk

-源码解析
            queryset = self.filter_queryset(self.get_queryset()) #返回所有数据queryset对象
            # lookup_url_kwarg就是pk,路由中有名分组分出来的pk
            lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
            # {pk:4}  4 浏览器地址中要查询的id号http://127.0.0.1:8000/books6/4/
            filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
            # 根据pk=4去queryset中get单个对象
            obj = get_object_or_404(queryset, **filter_kwargs)
            self.check_object_permissions(self.request, obj)
            return obj

所以,如果想改url传过来的关键字名称,在get_object()所在视图中添加

lookup_url_kwarg = "要设置的关键字名称"

GenericAPIView使用示例

urls.py

from django.contrib import admin
from django.urls import path,re_path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    # 使用GenericAPIView
    path('books2/', views.BookView2.as_view()),
    re_path('books2/(?P<pk>\d+)/', views.Book2DetailView.as_view()),  # 必须使用有名分组而且关键字为pk
]

views.py

from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from .models import Book
from app01.serializers import BookSerializer


# ===========================================================================
# 基于GenericAPIView写
class BookView2(GenericAPIView):
    # queryset要传queryset对象,查询了所有的图书
    # serializer_class使用哪个序列化类来序列化这堆数据
    queryset = Book.objects
    # queryset = Book.objects.all()
    serializer_class = BookSerializer
    def get(self, request):
        book_list = self.get_queryset()
        serializer = self.get_serializer(book_list,many=True)
        return Response(serializer.data)

    def post(self, request):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        else:
            return Response({'status': 101, 'msg': '添加失败'})


class Book2DetailView(GenericAPIView):
    queryset = Book.objects
    serializer_class = BookSerializer
    def get(self, request,pk):
        book = self.get_object()
        book_ser = self.get_serializer(book)
        return Response(book_ser.data)

    def put(self, request,pk):
        book = self.get_object()
        book_ser = self.get_serializer(instance=book,data=request.data)
        if book_ser.is_valid():
            book_ser.save()
            return Response(book_ser.data)
        else:
            return Response({'status': 101, 'msg': '校验失败'})

    def delete(self,request,pk):
        ret=self.get_object().delete()
        return Response({'status': 100, 'msg': '删除成功'})

models.py

from django.db import models


# Create your models here.
class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    publish = models.CharField(max_length=32)

serializers.py

from rest_framework import serializers
from app01.models import Book


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = "__all__"

2.2.4基于GenericAPIView 的5个视图扩展类写的接口

urls.py

from django.contrib import admin
from django.urls import path,re_path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    # 使用GenericAPIView
    path('books2/', views.BookView2.as_view()),
    re_path('books2/(?P<pk>\d+)/', views.Book2DetailView.as_view()),  # 必须使用有名分组而且关键字为pk
    # 使用GenericAPIView 5 个视图扩展类  重写的
    path('books3/', views.Book3View.as_view()),
    re_path('books3/(?P<pk>\d+)', views.Book3DetailView.as_view()),
]

views.py

from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from .models import Book
from app01.serializers import BookSerializer
from rest_framework.mixins import ListModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin, RetrieveModelMixin


# Create your views here.
# ===========================================================================
# 基于GenericAPIView写
class BookView2(GenericAPIView):
    # queryset要传queryset对象,查询了所有的图书
    # serializer_class使用哪个序列化类来序列化这堆数据
    queryset = Book.objects
    # queryset = Book.objects.all()
    serializer_class = BookSerializer

    def get(self, request):
        book_list = self.get_queryset()
        serializer = self.get_serializer(book_list, many=True)
        return Response(serializer.data)

    def post(self, request):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        else:
            return Response({'status': 101, 'msg': '添加失败'})


class Book2DetailView(GenericAPIView):
    queryset = Book.objects
    serializer_class = BookSerializer

    def get(self, request, pk):
        book = self.get_object()
        book_ser = self.get_serializer(book)
        return Response(book_ser.data)

    def put(self, request, pk):
        book = self.get_object()
        book_ser = self.get_serializer(instance=book, data=request.data)
        if book_ser.is_valid():
            book_ser.save()
            return Response(book_ser.data)
        else:
            return Response({'status': 101, 'msg': '校验失败'})

    def delete(self, request, pk):
        ret = self.get_object().delete()
        return Response({'status': 100, 'msg': '删除成功'})


# ===================================================================
# 基于GenericAPIView和5个视图扩展类写的接口
class Book3View(GenericAPIView, ListModelMixin, CreateModelMixin):
    queryset = Book.objects
    serializer_class = BookSerializer

    def get(self, request):
        return self.list(request)

    def post(self, request):
        return self.create(request)


class Book3DetailView(GenericAPIView, RetrieveModelMixin, DestroyModelMixin, UpdateModelMixin):
    queryset = Book.objects
    serializer_class = BookSerializer

    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)

2.2.5、GenericAPIView的视图子类

from rest_framework.generics import CreateAPIView

urls.py

from django.contrib import admin
from django.urls import path,re_path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    # 使用GenericAPIView 视图扩展类
    path('books4/', views.Book4View.as_view()),
    re_path('books4/(?P<pk>\d+)', views.Book4DetailView.as_view()),
]

views.py

from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from .models import Book
from app01.serializers import BookSerializer
from rest_framework.generics import CreateAPIView,ListAPIView,UpdateAPIView,RetrieveAPIView,DestroyAPIView


# Create your views here.
# ====================================
# 视图子类
class Book4View(ListAPIView,CreateAPIView):   # 获取所有和新增一个
    queryset = Book.objects
    serializer_class = BookSerializer


class Book4DetailView(RetrieveAPIView,UpdateAPIView,DestroyAPIView):
    queryset = Book.objects
    serializer_class = BookSerializer

补充:pycharm中查看类的继承关系

2.2.6 ModelViewSet使用

views.py

from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from .models import Book
from app01.serializers import BookSerializer
from rest_framework.viewsets import ModelViewSet


# Create your views here.
# ===========================================================
# 使用ModelViewSet
class Book5View(ModelViewSet):  # 5个接口都有,但是路由有点问题
    queryset = Book.objects
    serializer_class = BookSerializer

urls.py

from django.contrib import admin
from django.urls import path,re_path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    # 使用ModelViewSet
    path('books5/', views.Book5View.as_view(actions={'get':'list','post':'create'})), #当路径匹配,又是get请求,会执行Book5View的list方法
    re_path('books5/(?P<pk>\d+)', views.Book5View.as_view(actions={'get':'retrieve','put':'update','delete':'destroy'})),
]

2.2.6.1 源码分析ModelViewSet的父类ViewSetMixin

# 重写了as_view
# 核心代码(所以路由中只要配置了对应关系,比如{'get':'list'}),当get请求来,就会执行list方法
for method, action in actions.items():
    #method:get
    # action:list
    handler = getattr(self, action)
    #执行完上一句,handler就变成了list的内存地址
    setattr(self, method, handler)
    #执行完上一句  对象.get=list
    #for循环执行完毕 对象.get:对着list   对象.post:对着create

2.2.6.2 继承ViewSetMixin的视图类

1、视图

# views.py
from rest_framework.viewsets import ViewSetMixin
class Book6View(ViewSetMixin,APIView): #一定要放在APIVIew前
    def get_all_book(self,request):
        print("xxxx")
        book_list = Book.objects.all()
        book_ser = BookSerializer(book_list, many=True)
        return Response(book_ser.data)

# urls.py
    #继承ViewSetMixin的视图类,路由可以改写成这样
    path('books6/', views.Book6View.as_view(actions={'get': 'get_all_book'})),

2、自动生成路由

# views.py
from rest_framework.viewsets import ViewSetMixin, ViewSet
from luffyapi.utils.response import APIResponse
from rest_framework.decorators import action  # 装饰器
from . import serializers
# 登录接口
class LoginView(ViewSet):
    @action(methods=['POST'], detail=False)
    def login(self, request, *args, **kwargs):
        # 1 需要 有个序列化的类
        login_ser = serializers.LoginModelSerializer(data=request.data, context={'request': request})
        # 2 生成序列化类对象
        # 3 调用序列号对象的is_validad
        # result = login_ser.is_valid()
        if not login_ser.is_valid():
            return APIResponse(status=101, msg='登录失败', errors=login_ser.errors)

        token = login_ser.context.get('token')
        user = login_ser.context.get('user')
        # 4 return
        return APIResponse(status=100, msg='登录成功', token=token, username=user.username)

# urls.py
from django.urls import path,re_path,include
from . import views
from rest_framework.routers import SimpleRouter


router = SimpleRouter()
router.register('', views.LoginView, 'login')

urlpatterns = [
    path('', include(router.urls)),
]

小练习

1 自定义User表,新增mobile唯一约束字段;新增icon图片字段
2 在自定义User表基础上,用 GenericViewSet + CreateModelMixin + serializer 完成User表新增接口(就是注册接口)(重要提示:序列化类要重写create方法,不然密码就是明文了)
3 在自定义User表基础上,用 GenericViewSet + RetrieveModelMixin + serializer 完成User表单查(就是用户中心)
4 在自定义User表基础上,用 GenericViewSet + UpdateModelMixin + serializer 完成用户头像的修改

代码:

  • models.py
from django.db import models

# Create your models here.
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    mobile=models.CharField(max_length=32,unique=True)  #唯一
    icon=models.ImageField(upload_to='icon',default='icon/default.png')  #需要配media文件夹,上传的文件就会放到media文件夹下的icon


class Book(models.Model):
    name = models.CharField(max_length=64)

    def __str__(self):
        return self.name


class Car(models.Model):
    name = models.CharField(max_length=64)

    def __str__(self):
        return self.name
  • settings.py
MEDIA_URL='/media/'
MEDIA_ROOT=os.path.join(BASE_DIR,'media')   #真正的文件夹路径

#指定使用的auth表是自己定义的
AUTH_USER_MODEL='api.User'   #app名.表名
  • 总路由
from django.contrib import admin
from django.urls import path,re_path,include
from django.views.static import serve    # 暴露接口使用
from django.conf import settings  # 以后都用这个导配置文件,暴露接口

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('api.urls')),
    re_path(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})

]
  • api/urls.py
from django.urls import path,re_path,include
from api import views
from rest_framework.routers import SimpleRouter


router = SimpleRouter()
router.register('users', views.UserView,'users')
# 可以注册很多

urlpatterns = [
    # path('users/', views.UserView.as_view(action={'post':'create'})),  # 有问题
    path('', include(router.urls)),  # 第二种方式
    # 暴露后端指定文件夹资源,这样就会把files暴露给外界
]

# urlpatterns+=router.urls  # 一种方式
  • views.py
from django.shortcuts import render
from rest_framework.viewsets import GenericViewSet
# ViewSetMixin:重写了as_view,路由配置变样了, generics.GenericAPIView:只需要配俩东西
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin
from api import models
from api import ser


# Create your views here.
class UserView(GenericViewSet, CreateModelMixin, RetrieveModelMixin):
    queryset = models.User.objects.all()
    serializer_class = ser.UserModelSerializer

    # 假设get请求和post请求,用的序列化类不一样,如何处理?
    # 重写getget_serializer_class,返回啥,用的序列号类就是啥
    # 注册,用的序列化类是UserModelSerializer,查询一个用的序列化类是UserReadOnlyModelSerializer
    def get_serializer_class(self):
        print(self.action)  # create,retrieve
        if self.action == 'create':
            return ser.UserModelSerializer
        elif self.action == 'retrieve':
            return ser.UserReadOnlyModelSerializer
        elif self.action == 'update':
            return ser.UserImageModelSerializer
  • ser.py
from rest_framework import serializers
from api import models
from rest_framework.exceptions import ValidationError


class UserModelSerializer(serializers.ModelSerializer):
    re_password = serializers.CharField(max_length=16, min_length=4, required=True,
                                        write_only=True)  # 因为re_password在表中没有,需要在这定义

    class Meta:
        model = models.User
        fields = ['username', 'password', 'mobile', 're_password', 'icon']
        extra_kwargs = {
            'username': {'max_length': 16},
            'password': {'write_only': True}
        }

    # 局部钩子
    def validate_mobile(self, data):
        if not len(data) == 11:
            raise ValidationError('手机号不合法')
        return data

    # 全局钩子
    def validate(self, attrs):
        if not attrs.get('password') == attrs.get('re_password'):
            raise ValidationError('两次密码不一致')
        attrs.pop('re_password')  # 剔除该字段,因为数据库没有这个字段
        return attrs

    def create(self, validated_data):
        # attrs.pop('re_password') 如果上面没有剔除,在这也可以
        # models.User.objects.create(**validated_data) 这个密码不会加密
        user = models.User.objects.create_user(**validated_data)
        return user


class UserReadOnlyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.User
        fields = ['username', 'icon']


class UserImageModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.User
        fields = ['icon']
文档更新时间: 2022-04-03 18:21   作者:李延召