DRF学习笔记

1、关于REST

1.1、什么是REST

什么是REST?全称是 Resource Representational State Transfer。通俗来讲就是:资源在网络中以某种表现形式进行状态转移。
分解开来:
Resource:资源,即数据(前面说过网络的核心)。比如 books;
Representational:某种表现形式,比如用JSON,XML,JPEG等;
State Transfer:状态变化。通过HTTP动词GET、PUT、POST等来实现。

以前的网页都是前后端融合到一体的。但近年来随着移动互联网的发展,各种类型的客户端层出不穷,导致需要很多种接口才能满足需求。这样不利于开发工作。故前后端分离模式应运而生。

API接口本质上就是一些预先定义好的函数,用以实现不同组件之间的交互。
API协议就是两个不同组件完成交互所遵循的规则。
 
前后端不分离:后端从数据库拿数据,渲染模板,返回的是HTML页面
前后端分离:后端对外提供统一的API接口,可对接浏览器或移动端APP。后端返回的是json或xml格式的数据,不负责前端页面的处理。(xml基本不用了)

而RESTful 风格的API提供了一套统一的接口来满足Web,IOS,Android的需求,非常适合于前后端分离的项目。

1.2、RESTful规范

RESTful规范:
1、对于资源的具体操作类型由HTTP动词表示。/后指的是对应的SQL命令。常用四个:
get/select:从服务器取资源
post/create:从服务器新增资源
put/update:修改更新资源
delete/delete:从服务器删除资源
2、资源作为网址,使用与数据库表名相对应的名词复数形式,不能用动词;
如 GET/books
POST/books
PUT/books/1
DELETE/books/2
3、通过url参数的形式来过滤资源
如 GET /books/?limit=10
4、除了删除动作,其他都要求有响应体,尽量返回json格式的数据。
其他略、、、

RESTful风格的API的好处:

看Url就知道要什么;
看http method就知道干什么;
看http status code就知道结果如何。

1.3、django原生接口

下面使用django原生形式实现前后端分离的RESTful架构的API。

正常情况下,如果使用django去写一个图书管理的后端,需要很多接口。包括查询所有图书的接口,查询指定图书的接口,新增图书接口,修改图书的接口,删除图书的接口。这里用CBV类视图来实现。

根据路由后面有没有带pk/id可将这些接口分为两类,即不带pk的列表视图和带pk的详情视图。

列表视图内有get查询所有图书的接口和post新增单一图书的接口。如下:

 1 class BookListView(View):
 2     """列表视图"""
 3 
 4     def get(self,request):
 5         """查询所有图书接口"""
 6         # 1、查询所有图书模型
 7         books = Book.objects.all()
 8         # 2、遍历查询集,取出里面的每个书籍模型对象,转化为字典
 9         book_list = []
10         for book in books:
11             book_dict = {
12                 'nid':book.nid,
13                 'name':book.name,
14                 'price':book.price,
15                 'author':book.author
16             }
17             book_list.append(book_dict)
18         # 3、响应
19         return JsonResponse(book_list,json_dumps_params={'ensure_ascii':False},safe=False) # 将字典序列化为json
20         # JsonResponse类的data参数默认为字典,safe参数默认为true,如果不是字典则应该设置safe=False
21 # 注意:json_dumps_params参数默认为None,此时如果响应的字典中包含中文,则在客户端显示的是Unicode字符集的16进制码位,如\u8c;
22 # 如果想要显示中文,则应将ensure_ascii的默认参数True改为False。
23 
24     def post(self,request):
25         """新增图书接口"""
26         # 要先在settings.py中关闭CSRF验证
27         # 1、获取json格式的请求数据的字节流,由utf-8编码的16进制表示 如\x6c
28         json_bytes = request.body
29         # print(request.body)
30         # for k,v in request.environ.items():  # 打印请求头信息
31         #     print(k,v)
32         # 2、将字节流转化为json格式的字符串
33         json_str = json_bytes.decode()
34         # 3、将字符串转化为json格式的字典
35         json_dict = json.loads(json_str)
36         # 4、新增模型数据
37         book = Book(
38             name=json_dict['name'],
39             price=json_dict['price'],
40             author=json_dict['author']
41         )
42         # 5、保存模型
43         book.save()
44         # 6、响应字典
45         jd = {
46             'nid':book.nid,
47             'name':book.name,
48             'price':book.price,
49             'author':book.author
50         }
51         return  JsonResponse(jd,json_dumps_params={'ensure_ascii':False},status=201)

 

详情视图

 1 class BookDetailView(View):
 2     """详情视图"""
 3     def get(self,request,pk):
 4         """查询指定的图书接口"""
 5         # 1、获取指定pk的模型对象;
 6         try:
 7             book = Book.objects.get(nid=pk)  # id超出范围的异常处理
 8         except Book.DoesNotExist:
 9             print('查询数据不存在')
10             return HttpResponse("查询数据不存在", status=404)
11 
12         # 2、将模型对象转换为字典;
13         book_dict = {
14             'nid':book.nid,
15             'name':book.name,
16             'price':book.price,
17             'author':book.author
18         }
19         # 3、响应字典给前端
20         return JsonResponse(book_dict,json_dumps_params={"ensure_ascii":False})
21 
22     def put(self,request,pk):
23         """修改指定的图书接口"""
24         # 1、获取指定pk的模型对象
25         try:
26             book = Book.objects.get(nid=pk)  # id超出范围的异常处理
27         except Book.DoesNotExist:
28             return HttpResponse("要修改的数据不存在", status=404)
29         # 2、获取并将请求的数据转换为json字典
30         book_dict = json.loads(request.body.decode())
31         # 3 、修改模型对象并保存
32         book.name = book_dict['name']
33         book.price = book_dict['price']
34         book.author = book_dict['author']
35         book.save()
36         # 4、将修改后的模型对象转换为字典
37         book_modified_dict = {
38             'nid': book.nid,
39             'name': book.name,
40             'price': book.price,
41             'author': book.author
42         }
43         # 5、响应字典
44         return JsonResponse(book_modified_dict,json_dumps_params={'ensure_ascii':False})
45 
46     def delete(self,request,pk):
47         try:
48             book = Book.objects.get(nid=pk)
49         except Book.DoesNotExist:
50             return  HttpResponse('要删除的数据不存在',status=404)
51         book.delete()
52         return HttpResponse(status=204)  # 请求执行成功,但没有数据返回

呵呵

2、DRF实现接口

2.1、DRF初登场

从上面几个接口的代码可以看到,除了删除接口,其他的接口都有响应体,都会涉及到模型转字典这个操作,即序列化。新增和修改两个接口还会对请求体的数据进行字典转模型即反序列化操作,这就导致了代码的重复。
DRF将序列化和反序列化的业务逻辑进行了封装,简化了代码,大大提高了开发效率。
DRF是基于django的一个扩展。
安装:pip install djangorestframework。
注册:以django子应用的形式进行注册。

用drf来实现上面的图书接口,最简单版本只需要10行代码:

1、在urls.py中注册路由:

2、新建ser.py,添加序列化器:

3、在views.py中写入类视图:

浏览器访问:

 

 

 

 

 

为何如此简洁的代码就能实现接口功能呢?这就因为dfr内部对数据之间的转换进行了一系列的封装,这就涉及到序列化和反序列化。

序列化和反序列化:

当需要给前端响应模型数据时,需要将模型数据序列化成前端需要的格式。

当需要将用户发送的数据存储到数据库时,就要将数据如字典、json、xml等反序列化成Django中的数据库模型类对象再保存。

这就是在开发RESTful API时,在视图中要做的最核心的事情。

序列化和反序列化都是通过序列化器这个类来实现的。详情见下面的实例部分的serializer.py。

2.2、DRF实例-手机列表

2.2.1、模型models.py

 

from django.db import models
import django
# Create your models here.
# demo of DRF

class CellPhone(models.Model):
    id = models.AutoField(primary_key=True,verbose_name='手机ID')
    name = models.CharField(max_length=32,verbose_name='手机名')
    price = models.DecimalField(max_digits=7,decimal_places=2,verbose_name='手机价格')
    company = models.CharField(max_length=32,verbose_name='生产公司')

    class Meta:
        db_table = 'cellphone'

    def __str__(self):
        return self.name

 

2.2.2、视图view.py 

from django.shortcuts import render
# Create your views here.

from django.http import JsonResponse, HttpResponse, HttpRequest, Http404
from .models import CellPhone
from django.views import View
import json
# import django
# import os
# os.environ.setdefault("DJANGO_SETTINGS_MODULE","django01.settings")
# django.setup()
'''
# 使用django的原生框架来写rest风格的API接口
class CellphoneListView(View):
    """列表视图,不用传参"""
    def get(self,request):
        """获取全部接口"""
        # 1、获取查询集
        cps = CellPhone.objects.all()
        # 2、将查询集中的每个模型对象转字典嵌套到列表中
        cps_list = []
        for cp in cps:
            cp_dict = {
                'id':cp.id,
                'name':cp.name,
                'price':cp.price,
                'company':cp.company
            }
            cps_list.append(cp_dict)
        return JsonResponse(cps_list,safe=False)

    def post(self,request):
        """新增一个接口"""

        # 1、将请求中的字节流转字符串转json字典
        cp_dict = json.loads(request.body.decode())
        # 2、新增模型
        cp_model = CellPhone.objects.create(**cp_dict)
        # 3、将模型转为响应的字典
        cp_dict_return = {
            'id':cp_model.id,
            'name':cp_model.name,
            'price':cp_model.price,  # 传入价格3999,则返回的也是3999,不是3999.00
            'company':cp_model.company
        }

        return JsonResponse(cp_dict_return)

class CellphoneDetailView(View):
    """详情视图,需要传参"""
    def get(self,request,pk):
        """获取一个指定接口"""
        # 1、获取指定pk的模型对象
        # 对输入的id做一个简单的验证
        print(type(pk))
        print(pk)
        try:
            cp_model = CellPhone.objects.get(id=pk)
            print(cp_model)
        except CellPhone.DoesNotExist:
            return HttpResponse("查询数据不存在",status=404)
        # 2、将模型对象转字典
        cp_dict_return = {
            'id': cp_model.id,
            'name': cp_model.name,
            'price': cp_model.price,  # 传入价格3999,则返回的也是3999,不是3999.00
            'company': cp_model.company
        }
        # 3、响应字典给前端
        return JsonResponse(cp_dict_return)

    def put(self,request,pk):
        """修改一个指定的接口"""
        # 1、获取模型对象
        try:
            cp_model = CellPhone.objects.get(id=pk)
        except CellPhone.DoesNotExist:
            return HttpResponse("要修改的数据不存在",status=404)
        # 2、请求数据转字典
        cp_dict = json.loads(request.body.decode())
        # 3、字典转模型保存到数据库
        cp_model.name = cp_dict['name']
        cp_model.price = cp_dict['price']
        cp_model.company = cp_dict['company']
        cp_model.save()
        # 4、将修改后的模型转字典响应给前端
        cp_dict_return = {
            'id': cp_model.id,
            'name': cp_model.name,
            'price': cp_model.price,
            'company': cp_model.company
        }
        return JsonResponse(cp_dict_return)

    def delete(self,request,pk):
        """删除指定接口"""
        try:
            cp_model = CellPhone.objects.get(id=pk)
        except CellPhone.DoesNotExist:
            return HttpResponse("要删除的数据不存在",status=404)
        cp_model.delete()
        return HttpResponse("删除成功",status=204)
    
'''

# ---------------------------------------------------------------------------------------
# 使用DRF来写rest风格的API接口
from .serializer import CellPhoneSerializer

class CellphoneListView(View):
    """列表视图,不用传参"""
    def get(self,request):
        """获取全部接口"""
        # 1、获取查询集
        cps = CellPhone.objects.all()
        print(cps) # <QuerySet [<CellPhone: P30pro>, <CellPhone: mate30 pro>, <CellPhone: 小米11>]>
        # 2、直接对查询集进行序列化,并且应当将many置为True,因为查询集合包含多条数据
        ser = CellPhoneSerializer(instance=cps, many=True)
        print(ser)
        print(ser.data)  # 由多个有序字典组成的列表
        return JsonResponse(ser.data,safe=False)  # 此处返回的是列表嵌套字典,而不是字典,应置safe为False
        # return JsonResponse({'name':'wangyi','age':10})  # 此处返回列表嵌套字典,应置safe为False

    def post(self,request):
        """新增一个接口"""
        '''
        # 使用序列化器后的写法
        # 1、获取新增的json数据并将其转换为字典
        cp_new_data = json.loads(request.body.decode())
        # 2、保存到数据库模型
        # create方法能创建新的模型对象并保存到数据库,然后返回新增的模型对对象
        cp = CellPhone.objects.create(**cp_new_data)
        # 3、对返回的模型对象进行序列化输出
        ser = CellPhoneSerializer(cp)
        return JsonResponse(ser.data)
        '''
        # nmb
        ser = CellPhoneSerializer(data=json.loads(request.body.decode()))
        # 校验数据
        # 如果序列化器中id字段有read_only=True,此处新增数据就无需传id
        if  ser.is_valid():
            # ser_model = CellPhone.objects.create(**ser.validated_data)  # 校验之后才有validated_data字典
            # ser = CellPhoneSerializer(ser_model) # 对新增的模型序列化

            # 对以上两步优化:数据库操作放在序列化器中进行,在这直接调用序列化器的save方法
            ser.save()
            # obj = ser.save() # 上面ser.save()执行后返回的是新增的模型类对象obj,可对obj进行序列化输出,或者直接对一开始的反序列化对象ser进行响应ser.data
            return JsonResponse(ser.data)
        else:
            return JsonResponse(ser.errors) # 校验失败后抛出错误信息

        # 下面是APIView中的高级写法,需要在序列化器里重写create方法
        # # 1、直接对请求数据进行反序列化,给data传参,注意要指明data,因为默认第一个参数是instance
        # ser = CellPhoneSerializer(data=request.data)  # 无实例instance
        # print(ser)
        # print(request.body)  # 字节流类型
        # # 2、校验数据
        # if ser.is_valid():
        #     ser.save()   # 校验通过,调用序列化器里面的save()方法保存到数据库
        #     return JsonResponse(ser.data) # 返回新增的数据,json格式
        # else:
        #     return JsonResponse(ser.errors) # 校验未通过则返回错误信息


class CellphoneDetailView(View):
    """详情视图,需要传参"""

    # 对pk值校验,抽离出方法
    def get_object(self,pk):
        try:
            return CellPhone.objects.get(id=pk)
        except CellPhone.DoesNotExist:
            raise Http404
            # return HttpResponse("要修改的数据不存在", status=404)

    def get(self, request, pk):
        """获取一个指定接口"""
        # # 1、获取指定pk的模型对象
        # # 对输入的id做一个简单的验证
        # print(type(pk))
        # print(pk)
        # try:
        #     cp_model = CellPhone.objects.get(id=pk)
        #     print(cp_model)
        # except CellPhone.DoesNotExist:
        #     return HttpResponse("查询数据不存在",status=404)
        # # 2、将模型对象转字典
        # cp_dict_return = {
        #     'id': cp_model.id,
        #     'name': cp_model.name,
        #     'price': cp_model.price,  # 传入价格3999,则返回的也是3999,不是3999.00
        #     'company': cp_model.company
        # }
        # # 3、响应字典给前端
        # return JsonResponse(cp_dict_return)

        cp = self.get_object(pk)
        ser = CellPhoneSerializer(instance=cp)  # 也可省略instance=
        return JsonResponse(ser.data)

    def put(self,request,pk):
        """修改一个指定的接口"""
        # 1、获取模型对象
        cp_model = self.get_object(pk)
        # 2、请求数据转字典
        cp_dict = json.loads(request.body.decode())
        # 3、通过反序列化实行校验
        ser_r = CellPhoneSerializer(instance=cp_model,data=cp_dict)
        try:
            ser_r.is_valid(raise_exception=True)
        except Exception :
            # print(ser_r.errors)
            # print(ser_r.errors['name'])
            return JsonResponse(ser_r.errors)
        # 上面修改手机信息的时候不能修改手机name,否则因为唯一性校验失败
        # 怎么在修改的时候能够修改手机名呢??

        # 4、字典转模型保存到数据库
        # cp_model.name = ser_r.validated_data['name']
        # cp_model.price = ser_r.validated_data['price']
        # cp_model.company = ser_r.validated_data['company']
        # cp_model.save()
        # 通过给序列化对象同时传参instance和data后,就可以直接调用序列化对象的save()方法保存到数据库
        # 需要在我们定义的序列化器中重写update()方法供save去调用
        ser_r.save()

        # 5、将修改后的模型转字典响应给前端
        # cp_dict_return = {
        #     'id': cp_model.id,
        #     'name': cp_model.name,
        #     'price': cp_model.price,
        #     'company': cp_model.company
        # }
        # 5、序列化输出
        # ser = CellPhoneSerializer(cp_model)
        return JsonResponse(ser_r.data)

    def delete(self,request,pk):
        """删除指定接口"""
        cp_model = self.get_object(pk)
        cp_model.delete()
        return HttpResponse("删除成功",status=204)

 

2.2.3、序列化器serializer.py

序列化器CellPhoneSerializer可以继承自Serializer,也可以继承自Serializer的子类ModelSerializer
  1、 继承自Serializer:
from rest_framework import serializers
from rest_framework.validators import UniqueValidator
from rest_framework.exceptions import ValidationError
from app02_serializer.models import CellPhone


# 自定义校验,放在validators列表里使用
def made_in_china(company):
    if company in ["三星","苹果"]:
        raise ValidationError("只支持中国手机厂商")


class CellPhoneSerializer(serializers.Serializer):
    id = serializers.IntegerField(label='ID',read_only=True)
    name = serializers.CharField(max_length=32,label='手机名',
         validators=[UniqueValidator(CellPhone.objects.all(),message="手机名称不能重复")])
    price = serializers.DecimalField(max_digits=7,decimal_places=2,label='手机价格')
    company = serializers.CharField(max_length=32,label='生产公司',validators=[made_in_china])

    # 定义的属性与模型类中的字段名要一一对应。少了的话会影响序列化输出和反序列化输入的内容。
    # label即为模型字段中的verbose_name
    # read_only=True表示序列化输出的时候有该字段,反序列化输入的时候不用传,write_only=True则刚好相反
    # 如果id的read_only=True,则修改的时候无法指定nid字段,自动新增,无法修改旧的
    # 可通过参数validators以列表的形式添加多个验证器
    # 也可以通过在序列化器内部以 validate_字段名 的形式来添加校验,这个校验顺序排在最后
    def validate_price(self,value):
        """局部钩子函数,单字段校验"""
        if float(value) <= 1000:
            raise  ValidationError("手机价格过低")
        else:
            return value  # 注意这里一定要返回

    def validate(self, attrs):
        """全局钩子函数,多字段校验"""
        if attrs["price"] >= 10000 and attrs["company"] is "小米":
            raise ValidationError("该款小米手机价格太高了")
        else:
            return attrs

    def update(self, instance, validated_data):
        """重写update方法,instance是数据库模型对象"""
        instance.name = validated_data.get("name")
        instance.price = validated_data.get("price")
        instance.company = validated_data.get("company")
        instance.save() # 这是django的ORM提供的save
        return instance

    # 将数据库的新增放在序列化器中实现,需要重写serializers.py中的BaseSerializer类的create()
    # CellPhoneSerializer继承自Serializer,而Serializer继承自BaseSerializer,所以这里才能实现重写
    def create(self, validated_data):
        """新增时需要重写create"""
        # 下面的create()调用的是query.py里的为ORM所提供的create接口
        return CellPhone.objects.create(**validated_data)

   2、继承自ModelSerializer:

# 使用ModelSerializer进一步优化

# 1、ModerSerializer能根据模型自动生成序列化器中的字段;
# 2、ModelSerializer能自动重写create和update方法。
# 3、对于某些特殊需求,如前端传进来的数据中有一个是我不想要新增的,这时就要自己重写create方法,在validated_data中删除这个数据,实现个性化处理数据来满足业务需求。
# 4、若果定义的序列化器没有数据库模型与之对应,就用Serializer。
# 5、建议前期都用Serializer

class CellPhoneModelSerializer(serializers.ModelSerializer):
    # 覆盖重写name字段
    # trim_whitespace=True 去除项目名前后的空格
    name = serializers.CharField(max_length=32, label='手机名', trim_whitespace=True,
                                 validators=[UniqueValidator(CellPhone.objects.all(), message="手机名称不能重复")],
                                 error_messages={'max_length':'手机名长度不能超过32个字节哦!'})

    class Meta:
        model = CellPhone
        fields = "__all__"  # 指定序列化的字段域,all表示所有
        # fields = ['id', 'name']   # 指定序列化的字段域
        # exclude = ['name', 'price'] # 指定不参加序列化的字段
        extra_kwargs = { 'price':{'default':'2999','write_only':True},
                         'company':{'error_messages':'公司名长度不超过32字节哦!'}}  # 指定额外参数

#  个性化校验部分跟上面一样
    def validate_price(self, value):
        """局部钩子函数,单字段校验"""
        if float(value) <= 1000:
            raise ValidationError("手机价格过低")
        else:
            return value  # 注意这里一定要返回

    def validate(self, attrs):
        """全局钩子函数,多字段校验"""
        if attrs["price"] >= 10000 and attrs["company"] is "小米":
            raise ValidationError("该款小米手机价格太高了")
        else:
            return attrs

 

 

2.2.4、 Request和Response

Request

REST framework 传入视图的request对象不再是Django默认的HttpRequest对象,而是REST framework提供的扩展了HttpRequest类的Request类的对象。

REST framework 提供了Parser解析器,在接收到请求后会自动根据Content-Type指明的请求数据类型(如JSON、表单等)将请求数据进行parse解析,解析为类字典对象保存到Request对象中。

Request对象的数据是自动根据前端发送数据的格式进行解析之后的结果。

无论前端发送的哪种格式的数据,都可以以统一的方式读取数据。

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

  1. 包含了解析之后的文件和非文件数据;

  2. 包含了对POST、PUT、PATCH请求方式解析后的数据;

  3. 利用了REST framework的parsers解析器,不仅支持表单类型数据,也支持JSON数据;

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

Response

REST framework提供了一个响应类Response,使用该类构造响应对象时,响应的具体数据内容会被转换(render渲染)成符合前端需求的类型。
REST framework提供了Renderer 渲染器,用来根据请求头中的Accept(接收数据类型声明)来自动转换响应数据到对应格式。如果前端请求中未进行Accept声明,则会采用默认方式处理响应数据,也可以通过配置来修改默认响应格式。

默认方式:rest_framework/settings.py 

 

可以看出drf默认渲染器类里有两个渲染器:一个json渲染器,一个是可浏览的API渲染器。

当用postman去访问接口时,会调用json渲染器响应json格式的数据,而当我们用浏览器去访问接口时会返回可浏览的API渲染器。

如果需要指定渲染器,可以在自己项目的settings.py中去重写这段代码。格式人家都交代好了:

 如果只想返回json,只需注释掉可浏览API渲染器:

 则浏览器访问:

 

 之前是这样的:

 

 

 也可以在视图类中通过添加render_class属性指定渲染器:

from rest_framework.renderers import JSONRenderer
renderer_classes = [JSONRenderer, ]

 

构造方式:

Response(data, status=None, template_name=None, headers=None, content_type=None)
data数据不是render处理之后的数据,只需传递python的内建类型数据即可,REST framework会使用renderer渲染器处理data。
data不能是复杂结构的数据,如Django的模型类对象,对于这样的数据我们可以使用Serializer序列化器序列化处理后(转为了Python字典类型)再传递给data参数。

参数说明:

  1. data: 为响应准备的序列化处理后的数据;
  2. status: 状态码,默认200;
  3. template_name: 模板名称,如果使用HTMLRenderer 时需指明;
  4. headers: 用于存放响应头信息的字典;
  5. content_type: 响应数据的Content-Type,通常此参数无需传递,REST framework会根据前端所需类型数据来设置该参数。

 

2.2.5、APIView

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

APIView与View的不同之处在于:

  1. 传入到视图方法中的是REST framework的Request对象,而不是Django的HttpRequeset对象;

  2. 视图方法可以返回REST framework的Response对象,视图会为响应数据设置(render)符合前端要求的格式;

  3. 任何APIException异常都会被捕获到,并且处理成合适的响应信息;

  4. 在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制。

支持定义的属性:

  1. authentication_classes 列表或元组,身份认证类
  2. permissoin_classes 列表或元组,权限检查类
  3. throttle_classes 列表或元组,流量控制类

继承APIView后的视图:

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import CellPhone
from .serializer import CellPhoneModelSerializer


class CellPhoneListAPIView(APIView):
    """列表视图"""
    def get(self,request):
        """查询所有"""
        cp = CellPhone.objects.all()
        ser = CellPhoneModelSerializer(instance=cp, many=True)
        return Response(ser.data)

    def post(self, request):
         """新增"""
         data = request.data
         ser = CellPhoneModelSerializer(data=data)
         ser.is_valid(raise_exception=True)
         ser.save()
         return Response(ser.data,status=status.HTTP_201_CREATED)

class CellPhoneDetailAPIView(APIView):
    """详情查询"""

    def get(self,request,pk):
        """单一查询"""
        try:
            cp = CellPhone.objects.get(id=pk)
        except CellPhone.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)

        ser = CellPhoneModelSerializer(instance=cp)
        return Response(data=ser.data)

    def put(self,request,pk):
        """修改单一"""
        try:
            cp = CellPhone.objects.get(id=pk)
        except CellPhone.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)
        ser = CellPhoneModelSerializer(instance=cp, data=request.data)
        ser.is_valid(raise_exception=True)
        ser.save()
        return Response(data=ser.data)

    def delete(self,request,pk):
        """删除单一"""
        try:
            cp = CellPhone.objects.get(id=pk)
        except CellPhone.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)
        cp.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

 

APIView中的代码还是不够简练,没有将模型和序列化器抽离出来,假如需要更改这两者,则要改很多地方。并且当响应数据体量过大时,也没有分页展示功能等,而APIView的子类GenericAPIView就加入了很多功能。

 

2.2.6、GenericAPIView

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

这个类使用时必须要定义两个属性:queryset数据查询集和serializer_class序列化器

 views.py :

from rest_framework.response import Response
from rest_framework import status
from .models import CellPhone
from .serializer import CellPhoneModelSerializer
from rest_framework.generics import GenericAPIView


# GenericAPIView
class CellPhoneListGeneric(GenericAPIView):
    """列表视图"""
    # 指定查询集
    queryset = CellPhone.objects.all()
    # 指定序列化器类
    serializer_class = CellPhoneModelSerializer

    def get(self,request):
        """查询所有"""
        # 获取查询集
        instance = self.get_queryset()   # 通过父类方法返回实例属性queryset
        # 获取序列化器对象
        ser = self.get_serializer(instance, many=True)  # 通过调用封装后的方法来返回序列化器对象,主要给mixin扩展类使用
        return Response(ser.data)

    def post(self,request):
        """新增"""
        ser = self.get_serializer(data=request.data)
        ser.is_valid(raise_exception=True)
        ser.save()
        return Response(ser.data, status=status.HTTP_201_CREATED)


class CellPhoneDetailGeneric(GenericAPIView):
    """详情视图"""
    queryset = CellPhone.objects.all()
    serializer_class = CellPhoneModelSerializer

    def get(self,request,pk):
        """单一查询"""
        # 获取模型对象
        instance = self.get_object()  # 无需传pk,内部已封装,拿不到自动抛404
        ser = self.get_serializer(instance)
        return Response(ser.data)

    def put(self,request,pk):
        """修改单一"""
        instance = self.get_object()
        ser = self.get_serializer(instance,request.data)
        return Response(ser.data)

    def delete(self,request,pk):
        """删除单一"""
        instance = self.get_object()
        instance.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

 

注意:

1、get_object()会默认使用APIView提供的check_object_permissions方法检查当前对象是否有权限被访问。

 

 2、get_serializer()方法会向序列化器对象的context属性以字典的形式添加三个数据对象request、format和view。这三个数据对象可以在定义序列化器时使用。

 request:当前视图的请求对象

 view:当前请求的类视图对象

 format:当前请求期望返回的数据格式

 

2.2.7、mixins扩展类

虽说到目前为止,这五个接口的代码已经很简洁了,但还是存在重复性的代码,还可以更加简洁,利用drf提供的mixins扩展类,一行代码就可以搞定。

mixins有五个扩展类:

1、查询所有:ListModeMixin

2、新增单一:CreateModelMixin

3、查询单一:RetrieveModelMixin

4、修改单一:UpdateModelMixin

5、删除单一: DestroyModelMixin

我们要做的就是继承这些扩展类并调用类里面的方法即可。

from rest_framework.mixins import ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin

class CellPhoneListGeneric(ListModelMixin,CreateModelMixin,GenericAPIView):
    """列表视图"""
    queryset = CellPhone.objects.all()
    serializer_class = CellPhoneModelSerializer

    def get(self,request):
        """查询所有"""
        return self.list(request)

    def post(self,request):
        """新增"""
        return self.create(request)


class CellPhoneDetailGeneric(RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin,GenericAPIView):
    """详情视图"""
    queryset = CellPhone.objects.all()
    serializer_class = CellPhoneModelSerializer

    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)

 

从mixins.py 里的五大类的具体代码可以看出,他帮我们之前做的工作都做了一遍,还更加详细:

 1 """
 2 Basic building blocks for generic class based views.
 3 
 4 We don't bind behaviour to http method handlers yet,
 5 which allows mixin classes to be composed in interesting ways.
 6 """
 7 from rest_framework import status
 8 from rest_framework.response import Response
 9 from rest_framework.settings import api_settings
10 
11 
12 class CreateModelMixin:
13     """
14     Create a model instance.
15     """
16     def create(self, request, *args, **kwargs):
17         serializer = self.get_serializer(data=request.data)
18         serializer.is_valid(raise_exception=True)
19         self.perform_create(serializer)
20         headers = self.get_success_headers(serializer.data)
21         return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
22 
23     def perform_create(self, serializer):
24         serializer.save()
25 
26     def get_success_headers(self, data):
27         try:
28             return {'Location': str(data[api_settings.URL_FIELD_NAME])}
29         except (TypeError, KeyError):
30             return {}
31 
32 
33 class ListModelMixin:
34     """
35     List a queryset.
36     """
37     def list(self, request, *args, **kwargs):
38         queryset = self.filter_queryset(self.get_queryset())
39 
40         page = self.paginate_queryset(queryset)
41         if page is not None:
42             serializer = self.get_serializer(page, many=True)
43             return self.get_paginated_response(serializer.data)
44 
45         serializer = self.get_serializer(queryset, many=True)
46         return Response(serializer.data)
47 
48 
49 class RetrieveModelMixin:
50     """
51     Retrieve a model instance.
52     """
53     def retrieve(self, request, *args, **kwargs):
54         instance = self.get_object()
55         serializer = self.get_serializer(instance)
56         return Response(serializer.data)
57 
58 
59 class UpdateModelMixin:
60     """
61     Update a model instance.
62     """
63     def update(self, request, *args, **kwargs):
64         partial = kwargs.pop('partial', False)
65         instance = self.get_object()
66         serializer = self.get_serializer(instance, data=request.data, partial=partial)
67         serializer.is_valid(raise_exception=True)
68         self.perform_update(serializer)
69 
70         if getattr(instance, '_prefetched_objects_cache', None):
71             # If 'prefetch_related' has been applied to a queryset, we need to
72             # forcibly invalidate the prefetch cache on the instance.
73             instance._prefetched_objects_cache = {}
74 
75         return Response(serializer.data)
76 
77     def perform_update(self, serializer):
78         serializer.save()
79 
80     def partial_update(self, request, *args, **kwargs):
81         kwargs['partial'] = True
82         return self.update(request, *args, **kwargs)
83 
84 
85 class DestroyModelMixin:
86     """
87     Destroy a model instance.
88     """
89     def destroy(self, request, *args, **kwargs):
90         instance = self.get_object()
91         self.perform_destroy(instance)
92         return Response(status=status.HTTP_204_NO_CONTENT)
93 
94     def perform_destroy(self, instance):
95         instance.delete()

 大大解放了双手,可歌可泣。

 

 

使用mixins扩展后还是不够简洁,各种方法还得自己写,其实还可以进一步简化,把定义方法的过程也交给其他扩展类如ListAPIView。

from rest_framework.generics import GenericAPIView,ListAPIView,CreateAPIView,ListCreateAPIView
from rest_framework.generics import RetrieveAPIView,UpdateAPIView,DestroyAPIView,RetrieveUpdateDestroyAPIView

这些扩展类继承自GenericAPIView和mixins扩展类:

 

 里面帮我们实现了相应的方法:

 

继承ListAPIView就不用再写 查询所有 的方法,继承CreateAPIView就不用写 新增 的方法,而ListCreateAPIView又继承自前面两者,所以所有方法都不用写:

只用六行代码即可搞定列表视图和详情视图里面的五个操作:查询所有、新增、查询单一、修改、删除!

 

2.3 视图集

2.3.1 Viewset

 从上面的视图代码可以看出,两个视图类中有重复的代码,而且这两个类不能合并,因为里面都有get()方法。但万能的DRF为我们解决了这个问题。

通过视图集ViewSet可以实现列表查询和单一查询共存在一个视图类里面。

from rest_framework.viewsets import ViewSet

class CellPhoneViewSet(ViewSet):
    def list(self,request):
        """查询所有"""
        instance = CellPhone.objects.all()
        ser = CellPhoneModelSerializer(instance,many=True)
        return Response(ser.data)

    def retrieve(self,request,pk):
        """查询单一"""
        try:
            instance = CellPhone.objects.get(id=pk)
        except CellPhone.DoesNotExist:
            return Response(status.HTTP_404_NOT_FOUND)
        ser = CellPhoneModelSerializer(instance)
        return Response(ser.data)

 

使用视图集ViewSet,可以将一系列逻辑相关的动作放到一个类中:

  • list() 提供一组数据
  • retrieve() 提供单个数据
  • create() 创建数据
  • update() 保存数据
  • destory() 删除数据

ViewSet视图集类不再实现get()、post()等方法,而是实现动作 action 如 list() 、create() 等。

视图集只在使用as_view()方法的时候,才会将action动作与具体请求方式对应上。

ViewSet主要通过继承ViewSetMixin来实现在调用as_view()时传入字典(如{'get':'list'})的映射处理工作。

在ViewSet中,没有提供任何动作action方法,需要我们自己实现action方法。

设置路由:

 

 

 

2.3.2 GenericViewSet

 

使用ViewSet通常并不方便,因为list、retrieve、create、update、destory等方法都需要自己编写,而这些方法与前面讲过的Mixin扩展类提供的方法同名,所以我们可以通过继承Mixin扩展类来复用这些方法而无需自己编写。但是Mixin扩展类依赖与GenericAPIView,所以还需要继承GenericAPIView。

GenericViewSet就帮助我们完成了这样的继承工作,继承自GenericAPIView与ViewSetMixin,在实现了调用as_view()时传入字典(如{'get':'list'})的映射处理工作的同时,还提供了GenericAPIView提供的基础方法,可以直接搭配Mixin扩展类使用。

 

from rest_framework.mixins import ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin

 如只实现两个查询方法就要继承三个类:

class CellPhoneViewSet(ListModelMixin,RetrieveModelMixin,GenericViewSet):
queryset = CellPhone.objects.all()
serializer_class = CellPhoneModelSerializer

 也是比较麻烦。如果有个类xxx继承自上面这些类,而我们只要继承自这个xxx类就好了。

2.3.3 ModelViewSet

ModelViewSet的存在就解决了以上痛点:

 使用后的视图代码:只要三行

 路由部分:

 

这样就回到了最初2.1中十行代码搞定最基本的DRF接口了。

 

权限

权限控制可以限制用户对于视图的访问和对于具体数据对象的访问。

在执行视图的dispatch()方法前,会先进行视图访问权限的判断。
在通过get_object()获取具体对象时,会进行对象访问权限的判断。

有时我们希望只有特定用户才能访问视图,如登录后的用户,怎么实现?

在项目主urls.py添加路由api,自动添加登陆退出功能

 

 然后在设置settings.py里添加权限设置,默认为AllowAny,改为认证用户,这样就只有登录用户才能访问视图获得数据了。

 

 

 

 登录的用户名和密码可以通过命令行: python manage.py createsuperuser 来设置。

上面是全局设置,也可以通过另一种方法,在视图中添加permission_classes属性来进行权限设置:

 

其中第二个MyPermission权限是我个人自定义的,如需自定义权限,需继承rest_framework.permissions.BasePermission父类,并实现以下两个任何一个方法或全部:

1、has_permission(self, request, view)

  是否可以访问视图, view表示当前视图对象,如果没有设置的话默认的是True,如果设置False则表示所有的用户都不能访问该视图。

2、has_object_permission(self, request, view, obj)

  是否可以访问数据对象, view表示当前视图, obj为数据对象,控制视图能够访问添加了权限控制类的数据对象。
  

 

 上面的权限表示用户没有权限访问id为1~5的数据对象。

 

3、部分源码分析

3.1、 as_view()

查看django原生view的源码:
 

 

 
查看APIView的源码:
 
CBV模式中的路由写法:

 

 

其中这个as_view()到底起了什么作用?
原来as_view()是django原生Views类的一个类函数。其层级关系如下:

 

 

该类函数返回了一个内部函数view(),在views.类名.as_view()中执行as_view()函数就是执行了这个view()函数。该函数主要是对CBV模式中的类的请求方法函数传递了请求对象的参数request和其他参数。

 

 

其中通过self.setup()的方式调用了as_view()下面的setup()函数,setup()函数中self.request = request里的self指的是调用setup()函数的那个实例对象即cls(),也就是self。所以我觉得setup()函数里面的三行代码完全可以直接写在as_view()里面,没必要单独抽出来放在新建的setup()里。

 

 

 
这个view()函数返回的是dispatch()函数的执行结果。这里类视图中并没有dispatch()函数,只能从父类View中寻找。其实就在as_view()的下边。
 
顺便补充hasattr()、getattr()和setattr()三个函数的用法:
1. hasattr(object, name)
  判断object对象中是否存在name属性,当然对于python的对象而言,属性包含变量和方法;有则返回True,没有则返回False;需要注意的是name参数是string类型,所以不管是要判断变量还是方法,其名称都以字符串形式传参;getattr和setattr也同样;
 
 
2. getattr(object, name[, default])
  获取object对象的属性的值,如果存在则返回属性值;如果不存在分为两种情况:一种是没有default参数时,会直接报错;第二种是给定了default参数,若对象本身没有name属性,则会返回给定的default值;如果给定的属性name是对象的方法,则返回的是函数对象,需要调用函数对象来获得函数的返回值;调用的话就是函数对象后面加括号,如func之于func();
  另外还需要注意,如果给定的方法func()是实例函数,则不能写getattr(A, 'func')(),因为fun()是实例函数的话,是不能用A类对象来调用的,应该写成getattr(A(), 'func')();实例函数和类函数的区别可以简单的理解一下,实例函数定义时,直接def func(self):,这样定义的函数只能是将类实例化后,用类的实例化对象来调用;而类函数定义时,需要用@classmethod来装饰,函数默认的参数一般是cls,类函数可以通过类对象来直接调用,而不需要对类进行实例化;
 
3. setattr(object, name, value)
  给object对象的name属性赋值value,如果对象原本存在给定的属性name,则setattr会更改属性的值为给定的value;如果对象原本不存在属性name,setattr会在对象中创建属性,并赋值为给定的value;
 
 
dispatch()方法如下:

 

 

 

 
判断请求方法的小写如get是否包含在View类的属性http_method_names,如果是,再获取CBV类视图中的对应的函数如get的内存地址,并赋值给handler,此时handler=get,最终返回handler的运行结果,即get(request)的运行结果。
如果请求方法不在View的类属性中或者CBV类视图里没有写入对应的请求方法处理函数,就会返回下面的http_method_not_allowed()函数的执行结果,抛出错误信息。
 
由此可以看出,如果有某个需求要指定特定的请求方法如 post,只需要在自己的类视图添加属性http_method_names = ['post']即可。
 
APIVIew
视图写法:

 

 

路由:

 

 

APIVIew类属于rest_framework下面的views.py,继承自django框架下的base.py里面的View类。
APIVIew类里也有个as_view()方法

 

 

 
APIView的as_view()中,view就等于是父类的as_view()的执行结果,也就是base.py里的view函数的内存地址了,两个view相同了。由于python一切皆对象,view.cls = cls相当于给view这个函数添加了个属性cls。此时view里的cls指的就是BooksAPIView这个类。
 
最后return csrf_exempt(view)表明只要继承了APIView,就没有了csrf认证。
禁用csrf认证可以通过装饰器完成,也可以在路由中直接调用,如FBV模式中出现path('test/',csrf_exempt(views.test))也很正常。
 
由于APIView的as_view()中也有dispatch()方法,所以APIView对View中的dispatch()方法进行了重写,使得最终view()函数调用的是重写后的dispatch()方法。
 
 1    def dispatch(self, request, *args, **kwargs):
 2         """
 3         `.dispatch()` is pretty much the same as Django's regular dispatch,
 4         but with extra hooks for startup, finalize, and exception handling.
 5         """
 6         self.args = args
 7         self.kwargs = kwargs
 8         request = self.initialize_request(request, *args, **kwargs)
 9         self.request = request
10         self.headers = self.default_response_headers  # deprecate?
11 
12         try:
13             self.initial(request, *args, **kwargs)
14 
15             # Get the appropriate handler method
16             if request.method.lower() in self.http_method_names:
17                 handler = getattr(self, request.method.lower(),
18                                   self.http_method_not_allowed)
19             else:
20                 handler = self.http_method_not_allowed
21 
22             response = handler(request, *args, **kwargs)
23 
24         except Exception as exc:
25             response = self.handle_exception(exc)
26 
27         self.response = self.finalize_response(request, response, *args, **kwargs)
28         return self.response

 

request = self.initialize_request(request, *args, **kwargs) 这句代码对原来的request进行了初始化封装,加入了很多东西如请求解析内容。使得现在的request不再是HttpRequest的对象,而是rest_framework/request.py模块里的Request类的对象。

 

 在Request类中,原本请求的request对象变成了类的一个受保护属性_request(不能通过from xx import xx的方式导入)。

补充:类中的私有属性__y是不能被类的对象和子类直接引用的,因为在python中,是通过改名的方式实现属性的私有的,python会在内部会将类A的私有属性__y改名为_A__y。所以使用对象a.__y找不到该属性的,而使用a._A__y是可以操作__y属性的。 这种语言特性叫做名称改写。

 

 

此时如果在类视图中print(request.method)会有什么结果?答案是能够正常获取到了请求的方法。为什么封装后request也能获得请求的方法?因为类Request对__getattr__方法进行了重写,当我们获取请求方法找不到时就会调用这个内建方法,而此时又重写了,在重写后的__getattr__里,又通过反射来获取到了self._request的属性,也就是原生request的method属性。

参考:https://www.cnblogs.com/wangyi0419/p/12592492.html

补充反射:

 

 问题:这里的except中的 return self.__getattribute__(attr)我没看懂什么意思,self不是不能直接调用__getattrbute__()吗?

 

 在类视图中打印request.data,也有输出:

 

 

data看起来像个属性,但其实是一个被@property装饰器装饰过的方法。他能返回多种格式的请求数据。

 
 

 

补充一下装饰器@property的作用:

第一:@property是为了让人方便的像调用属性一样去调用方法,不用带();

第二:与所定义的属性配合使用,这样可以防止属性被修改。让外部直接调用给定的方法名,而不知道该方法返回的真正的属性名。从而达到了隐藏属性名的作用,让用户进行使用的时候不能随便更改。

python默认的成员函数和成员变量都是公开的。可以通过加双下划线将属性变为私有,不过私有不绝对,也是可以通过_类名__属性名的方式访问和修改的。

 
 
 现在回到APIView中的dispatch(),此时的request已经封装好了。之后就进入三大认证模块等模块了。
 

 

 3.2、 关于save()

 在一个简单点注册接口中,只有post()方法需要实现,视图继承自CreateAPIView。

 

 

 CreateAPIView实现post(),返回中调用CreateModelMixin中的create()。

 

 调用perform_create(),调用序列化器中的save()

 

然而我的序列化器类没有save(),在祖宗类BaseSerializer中有save(),这里的save()返回的实例,内部调用create()

 create()是抽象类,需要重写。

 

我在注册序列化器中重写了create(),调用了create_user():

 

 在规范化邮箱用户名给密码加密后又调用了save()类:

 

 

在AbstractBaseUser类中的save()又调用了父类的save():

 父类是django原生的Model:

 

 

 

 

 
 
 
 
 
 
未完待续。。。
 
 
 
posted @ 2021-02-01 03:33  天青色wy  阅读(465)  评论(0编辑  收藏  举报