1 Restful

1.1 restful api

可以总结为一句话:REST是所有WEB应用都应该遵守的架构设计指导原则。 Representational State Transfer,翻译是“表现层状态转化”。 面向资源是REST最明显的特征,对于同一个资源的一组不同的操作。资源是服务器上一个可命名的抽象概念,资源是以名词为核心来组织的,首先关注的名词。REST要求,必须通过统一的接口来对资源执行各种操作。对于每个资源只能执行一组有限的操作。

1.2 Restful API 设计规范

  • 资源:首先是弄清楚资源的概念。资源就是网络上的一个实体,一段文本,一张图片或者一首歌曲。资源总是要通过一种载体来反应它的内容。文本可以用TXT,也可以用HTML或者XML、图片可以用JPG格式或者PNG格式,JSON是现在最常用的资源表现形式。
  • 统一接口。RESTful风格的数据元操CRUD分别对应HTTP方法:GET用来获取资源,POST用来新建资源(可以用于更新资源 ),PUT用来更新资源,DELETE用来删除资源,这样就统一了数据操作的接口。
  • URI。可以用一个URI(统一资源标识符)指向资源,即每个URI都对应一个特定的资源。要获取这个资源访问它的URI就可以,因此URI就成了每一个资源的地址或识别符。一般的,每个资源至少有一个URI与之对应,最典型的URI就是URL。
  • 无状态。所谓无状态即所有的资源都可以URI定位,而且这个定位与其他资源无关,也不会因为其他资源的变化而变化。有状态和无状态的区别:

    例如要查询员工工资的步骤为第一步:登录系统。第二步:进入查询工资页面。第三步:搜索该员工。第四步:点击姓名查看工资。这样的操作流程就是有状态的,查询工资的每一个步骤都依赖于前一个步骤,只要前置操作不成功,后续操作就无法执行。如果输入一个URL就可以得到指定员工的工资,则这种情况就是无状态的,因为获取工资不依赖于其他资源或状态,且这种情况下,员工工资是一个资源,由一个URI与之对应可以通过HTTP中GET方法得到资源,这就是典型的RESTFUL风格。

  • RESTful API还有其他一些规范。

    • 应该将API的版本号放入URL。GET:http://www.xx.com/v1/friend/123。或者将版本号放在HTTP头信息中。版本号取决于自己开发团队的习惯和业务的需要,不是强制的。
    • 另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github采用这种做法。版本号可以在HTTP请求头信息的Accept字段中进行区分:

      1. Accept: vnd.example-com.foo+json; version=1.0
    • 资源作为网址,只能有名词,不能有动词,而且所用的名词往往与数据库的表名对应。

      1. /getProducts
      2. /listOrders
      3. /retreiveClientByOrder?orderId=1

      对于一个简洁结构,你应该始终用名词。此外,利用HTTP方法可以分离网址中的资源名称的操作

      1. GET /products: 将返回所有产品清单
      2. POST /products: 将产品新建到集合
      3. GET /products/4: 将获取产品4
      4. PUT /products/4: 将更新产品4
    • API中的名词应该使用复数。无论子资源或者所有资源。

      1. 获取单个产品:http://127.0.0.1:8080/AppName/rest/products/1
      2. 获取所有产品:http://127.0.0.1:8080/AppName/rest/products
    • 如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果

      1. ?limit=10: 指定返回记录的数量
      2. ?offset=10: 指定返回记录的开始位置
      3. ?page=2&per_page=100: 指定第几页,以及每页的记录数
      4. ?sortby=name&order=asc: 指定返回结果按照哪个属性排序,以及排序顺序
      5. ?animal_type_id=1: 指定筛选条件

      1.3 restful架构简介

  • 每一个URL代表一种资源

  • 客户端和服务器之间,传递这种资源的某种表现层
  • 客户端通过四个HTTP动词,对服务端资源进行操作,实现“表现层状态转换”

    1.3.1 HTTP常用动词

    常用的HTTP动词有下面四个

  • GET(SELECT):从服务器取出资源

  • POST (CREATE or UPDATE): 在服务端创建资源或更新资源
  • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)
  • DELETE(DELETE): 从服务器删除资源

    还有三个不常用的HTTP动词

  • HEAD:获取资源的元数据

  • OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的
  • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)

    1.3.2 restful相关的网络请求状态码

    200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
    201 CREATED - [POST``/PUT/PATCH``]:用户新建或修改数据成功。
    202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
    204 NO CONTENT - [DELETE]:用户删除数据成功。
    400 INVALID REQUEST - [POST``/PUT/PATCH``]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
    401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
    403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
    404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
    406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
    410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
    422 Unprocesable entity - [POST``/PUT/PATCH``] 当创建一个对象时,发生一个验证错误。
    500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

    1.3.3 错误处理(Error handing)

    如果状态码是4xx,服务器就应该向用户出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。

  1. {error:"Invalid API key"}

1.3.4 返回结果

针对不同操作,服务器向用户返回的结果应该符合以下规范。

  1. GET /collection:返回资源对象的列表(数组)
  2. GET /collection/1:返回单个资源对象
  3. POST /collection:返回新生成的资源对象
  4. PUT /collection/1:返回完整的资源对象
  5. PATCH /collection/1:返回完整的资源对象

2 DRF框架

2.1 web应用模式

  • 前后端不分离

    在前后端不分离的引用模式中,前端页面看到的效果都是由后端控制的,由后端页面渲染或者重定向,也就是后端需要控制前端的展示,前端与后端的耦合度很高,这种模式比较适合纯网页应用。但是后端对接APP时,App可能并不需要后端返回一个HTML网页,二仅仅是数据本身,所以后端原本返回网页的接口不再适用前端APP应用,为了对接APP后端还需再开发一套接口。

image.png

  • 前后端分离

    在前后端分离的应用模式中,后端仅返回前端所需要的数据,不再渲染HTML页面,不再控制前端的效果,只要前端用户看到什么效果,从后端请求的数据如何加载到掐断中,都由前端自己决定,网页有网页自己的处理方式,APP有APP的处理方式,但无论哪种前端所需要的数据基本相同,后端仅需要开发一套逻辑对外提供数据即可,在前后盾分离的应用名欧式中,前端与后盾的耦合度相比较低。 在前后端分离的应用模式中,我们通常将后端开发的每个视图都称为一个接口,或者API,前端通过访问接口来对数据进行增删改查。

image.png

2.2 Django Rest Framework

Django Rest Framework(DRF) 是一个强大且灵活的工具包,用以构建Web API。Django Rest Framework可以在Django的基础上迅速实现API,并且本身还带有WEB的测试页面,可以方便的测试自己的API。 https://www.django-rest-framework.org/ https://q1mi.github.io/Django-REST-framework-documentation/

  • 特性
    • 可浏览API
    • 提供丰富认证
    • 支持数据序列化
    • 可以轻量嵌入,仅适用fbv
    • 强大的社区支持
  • 环境的安装的配置
    • DRF依赖于:Python(3.5,3.6,3.7,3.8)
    • Django(1.11,2.0,2.1,2.2,3.0)
  • DRF是以Django扩展应用的方式提供的,所以我们可以直接利用已有的Django环境而无需重新创建。

    安装

  • 安装djangorestframework

    1. pip3 install djangorestframework
  • 添加到应用

    1. INSTALLED_APPS = [
    2. ...
    3. 'rest_framework',
    4. ]
  • 404问题

    • https://my.oschina.net/u/4403815/blog/4094083

      2.3 使用Django开发REST接口,不使用DRF

      ```python

      urls.py

      from django.urls import path from . import views urlpatterns = [ path(‘book//‘, views.BooksView.as_view(), name=’book’), ]

      models.py

      class Bookinfo(models.Model): btitle = models.CharField(max_length=200) bpub_date = models.DateField(blank=True, null=True) bread = models.IntegerField() bcomment = models.IntegerField() bimage = models.CharField(max_length=200, blank=True, null=True) class Meta: db_table = ‘bookinfo’ def to_dict(self): return {
      1. 'btitle': self.btitle,
      2. 'bpub_date': self.bpub_date,
      3. 'bread': self.bread,
      4. 'bcomment': self.bcomment,
      5. 'bimage': self.bimage,
      }

class Heroinfo(models.Model): hid = models.AutoField(primary_key=True) hname = models.CharField(max_length=50) bid = models.ForeignKey(Bookinfo, models.DO_NOTHING, db_column=’bid’, blank=True, null=True)

class Meta:
    db_table = 'heroinfo'

views.py

class BooksView(View): def queryset_to_list(self, queryset): res = [] for obj in queryset: res.append(obj.to_dict()) return res

def get(self, request, *args, **kwargs):
    books = Bookinfo.objects.all()
    return JsonResponse(self.queryset_to_list(books), safe=False)

def post(self, request):
    data = request.POST.dict()
    Bookinfo.objects.create(**data)
    return JsonResponse({
        'code': 1, 'msg': '创建成功'
    },status=201)

def put(self, request, bid):
    try:
        book = Bookinfo.objects.get(pk=bid)
    except Bookinfo.DoesNotExist:
        return HttpResponse(status=404)
    book_dict = json.loads(request.body.decode())
    book.__dict__.update(book_dict)
    book.save()
    return JsonResponse({
        'code': 1, 'msg': '修改成功'
    })

def delete(self, request):
    pass
> 实现起来比较麻烦,费代码

<a name="06d73862"></a>
# 3 django-rest-framework
<a name="0RfrY"></a>
## 3.1 序列化
> 序列化可以把查询集合模型对象转换为json、xml或其他类型,也提供反序列化功能,也就是把转换后的类型转换为对象或查询集。
> REST框架中的序列化程序与Django Form和ModelForm类的工作方式非常相似。我们提供了一个Serializer类,它提供了一种强大的通用方法来控制响应的输出,以及一个ModelSerializer类,它提供了一个有用的快捷方式来创建处理模型实例和查询集的序列化程序。

<a name="onErw"></a>
### 3.1.1 声明序列化器
```python
from rest_framework import serializers

class xxxSerializer(serializers.Serializer):
    email = serializer.EmailField()
    content = serializer.CharField(max_length=200)
    created = serializer.DateTimeField()

3.1.2 常用field类

  • 核心参数 | 参数名 | 缺省值 | 说明 | | —- | —- | —- | | read_only | False | 表明该字段仅用于序列化输出,不能反序列化 | | required | True | 如果在反序列化期间未提供字段,则会引发错误。如果在反序列化期间不需要此字段,则设置为false | | default | | 缺省值,部分更新时不支持 | | allow_null | False | 表明该字段是否允许传入None,默认False | | validators | [] | 验证器,一个函数列表,验证通不过引起serializers.ValidationError | | error_messages | {} | 一个错误信息字典 |

  • 常用字段 | 字段名 | 说明 | | —- | —- | | BooleanField | 对应于django.db.models.fields.BooleanField | | CharField | CharField(max_length=None,min_length=None,allow_blank=False,trim_whitespace=True) trim_whitespace如果设置为True,则会修剪前导和尾随的空格 | | EmailField | EmailField(max_length=None,min_length=None,allow_blank=False) | | InterField | IntegerField(max_value=None,min_value=None) | | FloatField | FloatField(max_value=None,min_value=None) | | DateTimeField | DateTimeField(format=api_settings.DATETIME_FORMAT,input_formats=None,defualt_timezone=None)
    format格式字符串可以是显式指定格式的Python strftime格式,input_formats表示可用于解析日期的输入格式的字符串列表 |

3.1.3 创建Serializer对象

定义好Serializer类后,就可以创建Serializer对象了。其构造方法为: Serializer(instance=None,data=empty,**kwarg)

说明:

  1. 用于序列化时,将模型类对象传入instance参数
  2. 用于反序列化时,将要被反序列化的数据传入data参数
  3. 除了instance和data参数外,在构造Serializer对象时,还可通过context参数额外添加数据,如
    serializer = UserSerializer(User,context={'request':request})
    
    通过context参数附加的数据,可以通过Serializer对象context属性获取
  • 实例 ```python

    models.py

    class User(models.Model): username = models.CharField(max_length=30) password_hash = models.CharField(max_length=20, db_column=’password’) age = models.IntegerField(default=0)

    class Meta:

      db_table = 'user_drf'
    

    serializers.py

    def validate_username_exist(value): if User.objects.filter(username=value).first():

      raise serializers.ValidationError('%s 用户名已存在' % value)
    

    def validate_password(password): if re.math(r’\d+$’, password):

      raise serializers.ValidationError("密码不能是纯数字")
    

class UserSerializer(serializers.Serializer): id = serializers.IntegerField() username = serializers.CharField(required=True, validators=[ validateusername_exist, RegexValidator(regex=’^[a-zA-Z][a-zA-Z0-9]{3,18}$’, message=’支持大小写字母数字下划线短横线’)], error_messages={ ‘required’: ‘用户名必须输入’, ‘min_length’: ‘用户名至少3个字符’ }) password_hash = serializers.CharField(required=True, validators=[validate_password, RegexValidator(regex=r’^(?=.[a-z])(?=.[A-Z])(?=.*\d)[\w\W]{8,16}$’, message=’必须包含大小写字母数字。可特殊符号’, )]) age = serializers.IntegerField()

def create(self, validated_data):
    return User.objects.create(**validated_data)

def update(self, instance, validated_data):
    instance.username = validated_data.get('username', instance.username)
    instance.password_hash = validated_data.get('password_hash', instance.password_hash)
    instance.age = validated_data.get('age', instance.age)
    instance.save()
    return instance

views.py

class ExampleView(APIView): def get(self, request):

    # 1. 查询数据
    user = User.objects.get(pk=1)
    # 2. 构造序列化器
    serializer = UserSerializer(instance=user)
    # 获取序列化数据,通过data属性可以获取序列化后的数据
    print(serializer.data)
    return Response(serializer.data)
如果要被序列化的是包含多条数据的查询集QuerySet,可以通过添加many=True参数补充说明
```python
data = User.objects.all()
serializer = UserSerializer(instance=data,many=True)

3.1.4 ModelSerializer

ModelSerializer类能够让你自动创建一个具有模型中相应字段的Serializer类。这个ModelSerializer类和常规的Serializer类一样,不同的是:

  • 根据模型自动生成一组字段
  • 自动生成序列化器的验证器,比如unique_together验证器
  • 默认简单实现了.create()方法和.update()方法

    3.1.4.1 定义

    class BookInfoSerializer(serializer.ModelSerializer):
      """图书数据序列化器"""
      class Meta:
          model = BookInfo
          field = "__all__"
    
  • model指明参数哪个模型类

  • fields指明为模型类的哪些字段生成

    3.1.4.2 指定字段

  1. 使用fields来明确字段,all表名包含所有字段,也可以写明具体哪些字段,如

    class BookInfoSerializer(serializers.ModelSerializer):
     class Meta:
         model = BookInfo
         field = ('id','btitle','bpub_date')
    
  2. 使用exclude可以明确排除掉哪些字段

    class BookInfoSerializer(serializers.ModelSerializer):
     class Meta:
         model = BookInfo
         exclude = ('image')
    
  3. 指明只读字段

可以通过read_only_fields指明只读字段,即仅用于序列化输出的字段

class BookInfoSerializer(serializers.ModelSerializer):
    class Meta:
        model = BookInfo
        field = ('id','btitle','bpub_date','bread','bcomment')
        read_only_fields = ('id','bread','bcomment')

3.1.4.3 添加额外参数

可以使用extra_kwargs参数为ModelSerializer添加或修改原有的选项参数

class BookInfoSerializer(serializers.ModelSerializer):
    class Meta:
        model = BookInfo
        field = ('id','btitle','bpub_date','bread','bcomment')
        extra_kwargs = {
            'bread':{'min_value':0,'required':True}
        }

3.2 反序列化

3.2.1 验证

使用序列化器进行反序列化时,需要对数据进行验证后,才能获取验证成功的数据或保存成模型类对象。 在获取反序列化的数据前,必须调用is_valid()方法进行验证,验证成功返回True,否则返回False 验证失败,可以通过序列化器对象的errors属性获取错误信息,返回字典,包含了字段和字段的错误。如果是非字段错误,可以通过修改REST framework配置中的NON_FIELD_ERRORS_KEY来控制错误字典中的键名。 验证成功,可以通过序列化器对象的validated_data属性获取数据。 在定义序列化器时,指明每个字段的序列化类型和选项参数,本身就是一种验证行为。

class BookInfoSerializer(serializers.Serializer):
    """图书数据序列化器"""
    id = serializers.IntegerField(label='ID', read_only=True)
    btitle = serializers.CharField(label='名称', max_length=20)
    bpub_date = serializers.DateField(label='发布日期', required=False)
    bread = serializers.IntegerField(label='阅读量', required=False)
    bcomment = serializers.IntegerField(label='评论量', required=False)
    image = serializers.ImageField(label='图片', required=False)

通过构造序列化器对象,并将要反序列化的数据传递给data构造参数,进而进行验证

# 反序列化
data = {'bpub_date':123}
serializer=BookInfoSerializer(data=data)
serializer.is_valid() # 返回False
serializer.errors
# {'btitle':[ErrorDetail(string='This field is required.',code='required')],
'bpub_date':[ErrorDetail(string='Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]].',code='invalid')]}
serializer.validated_date # {}

data = {'btitle':'python'}
serializer=BookInfoSerializer(data=data)
serializer.is_valid() # 返回True
serializer.errors # {}
serializer.validated_data # OrderedDict(['btitle','python'])

is_valid()方法还可以在验证失败时抛出异常serializers.ValidationError,可以通过传递raise_exception=True参数开启,REST framework接收到此异常,会向前端返回HTTP 400 Bad Request响应。

# Return a 400 response if the data was invalid.
serializer.is_valid(raise_exception=True)

如果这些还不够用,需要再补充定义验证行为,可以使用以下三种方法:

3.2.1.1 validate_

字段进行验证,如

class BookInfoSerializer(serializers.Serializer):
    def validate_btitle(self,value):
        if 'django' not in value.lower():
            raise serializers.ValidationError("图书不是关于django")
        return value

3.2.1.2 validate

在序列化器中需要同时对多个字段进行比较验证时,可以定义validate方法来验证,如

class BookInfoSerializer(serializers.Serializer):
    def validate(self,attrs):
        bread = attrs['bread']
        bcomment= attrs['bcomment']
        if bread < bcomment:
            raise serializers.ValidationError('阅读量小于评论量')
        return attrs

3.2.1.3 validators

在字段中添加validators选项参数,也可以补充验证行为,如

def about_django(value):
    if 'django' not in value.lower():
        raise serializers.ValidationError("图书不是关于django")
class BookInfoSerializer(serializers.Serializer):
    btitle = serializers.CharField(label='名称',validators=[about_django])

3.2.2 保存

如果在验证成功后,想要基于validated_data完成数据对象的创建,可以通过实现create()和update()两个方法来实现

class BookInfoSerializer(serializers.Serializer):
    """图书数据序列化器"""
    bid = serializers.IntegerField(label='ID', read_only=True, help_text="主键")
    btitle = serializers.CharField(label='名称', max_length=20)
    bpub_date = serializers.DateField(label='发布日期', required=False)
    bread = serializers.IntegerField(label='阅读量', required=False)
    bcomment = serializers.IntegerField(label='评论量', required=False)
    bimage = serializers.CharField(label='图片', required=False)

    def create(self, validated_data):
        return Bookinfo.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.btitle = validated_data.get('btitle', instance.btitle)
        instance.bpub_date = validated_data.get('bpub_date', instance.bpub_date)
        instance.bread = validated_data.get('bread', instance.bread)
        instance.bcomment = validated_data.get('bcomment', instance.bcomment)
        instance.save()
        return instance

实现上述两个方法后,在反序列化数据的时候,就可以通过save方法来返回一个数据对象实例

book = serializer.save()

如果创建序列化器对象的时候,没有传递instance实例,则调用save()方法的时候,create()被调用,相反,如果传递了instance实例,则调用save()方法的时候,update()被调用

class BookInfoView(GenericAPIView):
    queryset = Bookinfo.objects.all()
    serializer_class = BookInfoSerializer

    def get(self, request, bid=-1):
        if bid < 0:
            return self.find_many(request=request)
        return self.find_one(request, bid)

    def find_many(self, request):
        bs = BookInfoSerializer(instance=self.queryset.all(), many=True)
        return Response(data=bs.data)

    def find_one(self, request, bid):
        book = self.queryset.filter(pk=bid).first()
        bs = BookInfoSerializer(instance=book)
        return Response(data=bs.data)

    def post(self, request):
        # print(request.data)
        # 反序列化
        # 将前端传过来的数据赋值给data
        bs = BookInfoSerializer(data=request.data)
        if bs.is_valid():  # 数据验证
            print(bs.validated_data)  # 获取验证数据
            bs.save()  # 保存数据库
            return Response({'code': 1, 'msg': 'success'})
        else:
            print(bs.errors)
            return Response({'code': 0, 'msg': bs.errors})

     def put(self, request, bid):
        book = Bookinfo.objects.get(id=bid)
        # 部分更新
        # data = request.data
        # for key, value in data.items():
        #     if hasattr(book, key):
        #         setattr(book, key, value)
        # book.save()
        # 序列化器是必须满足序列化要求的
        bs = BookInfoSerializer(book, data=request.data)
        if bs.is_valid():  # 数据验证
            print(bs.validated_data)  # 获取验证数据
            bs.save()  # 保存数据库
            return Response({'code': 1, 'msg': 'success'})
        else:
            print(bs.errors)
            return Response({'code': 0, 'msg': bs.errors})

说明:

  • 在对序列化器进行save()保存时,可以额外传递数据,这些数据可以在create()和update()中validated_data参数获取
    serializer.save(owner=request.user)
    

    3.3 关联对象的序列化

    如果模型中两个模型有外键关系,如果需要序列化的模型中包含有其他关联对象,则对关联对象数据的序列化需要指明。例如,在定义英雄数据的序列化器时,外键hbook(即所属的图书)字段如何序列化? 先定义HeroInfoSerialzer除外键字段外的其他部分:

class Bookinfo(models.Model):
    bid = models.AutoField(primary_key=True, db_column='id')
    btitle = models.CharField(max_length=200)
    bpub_date = models.DateField(blank=True, null=True)
    bread = models.IntegerField()
    bcomment = models.IntegerField()
    bimage = models.CharField(max_length=200, blank=True, null=True)
    class Meta:
        db_table = 'bookinfo'

class Heroinfo(models.Model):
    hid = models.AutoField(primary_key=True)
    hname = models.CharField(max_length=50)
    bid = models.ForeignKey(Bookinfo, db_column='bid', blank=True, null=True, on_delete=models.CASCADE,
                            related_name='heros')
    class Meta:
        db_table = 'heroinfo'

3.3.1 PrimaryKeyRelatedField

此字段将被序列化为关联对象的主键

class HeroInfoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Heroinfo
        fields = "__all__"

class BookInfoSerializer(serializers.ModelSerializer):
    heros = PrimaryKeyRelatedField(many=True, read_only=True)

    class Meta:
        model = Bookinfo
        fields = "__all__"
  • many=True,表示可以有多个主键值
  • read_only=True,该字段将不能用作反序列化使用

获取列表:

[
    {
        "bid": 1,
        "heros": [
            1,
            2
        ],
        "btitle": "射雕英雄传",
        "bpub_date": "2021-03-17",
        "bread": 30,
        "bcomment": 50,
        "bimage": "dog.jpg"
    }
]

3.3.2 StringRelatedField

此字段将被序列化为关联对象的字符串表示方式(即模型对象str方法的返回值)

hbook = serializers.StringRelatedField(many=True,read_only=True)
# models.py需要加上
    def __str__(self):
        return self.hname

获取:

[    {
        "bid": 1,
        "heros": [
            "小龙女",
            "杨过"
        ],
        "btitle": "射雕英雄传",
        "bpub_date": "2021-03-17",
        "bread": 30,
        "bcomment": 50,
        "bimage": "dog.jpg"
    }]

3.3.3 使用关联对象的序列化器

hreps = HeroInfoSerializer(many=True,read_only=True)

获取:

[
    {
        "bid": 1,
        "heros": [
            {
                "hid": 1,
                "hname": "小龙女",
                "bid": 1
            },
            {
                "hid": 2,
                "hname": "杨过",
                "bid": 1
            }
        ],
        "btitle": "射雕英雄传",
        "bpub_date": "2021-03-17",
        "bread": 30,
        "bcomment": 50,
        "bimage": "dog.jpg"
    },
]

3.4 Request和Response

3.4.1 Request

REST框架引入了一个扩展了常规HttpRequest的Request对象,并提供了更灵活的请求解析。Request对象的核心功能是request.data属性,它与request.POST类似,但对于使用Web API更为有用。

REST framework提供了Parser解析器,在接收到请求后会自动根据Content-Type指明的请求数据类型(如JSON、表单等)将请求数据进行parse解析,解析为类字典对象保存到Request对象中。
Request对象的数据是自动根据前端发送数据的格式进行解析之后的结果。
无论前端发送的哪种格式的数据,都可以以统一的方式读取数据

3.4.1.1 data属性

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

  • 包含了解析之后的文件和非文件数据
  • 包含了对POST、PUT、PATCH请求方式解析后的数据
  • 利用了REST framework的parsers解析器,不仅支持表单类型数据,也支持JSON数据
    request.POST # 只处理表单数据,适用于POST方法
    request.data # 处理任意数据,适用于POST/PUT/PATCH
    

    3.4.1.2 query_params查询参数

    request.query_params与Django标准的request.GET相同,只是更换了名字

    3.4.1.3 method

    request.method 返回请求的HTTP方法的大写字符串表示形式。

    3.4.1.4 自定义解析器

    REST framework的请求对象提供灵活的请求解析,允许以与通常处理表单数据相同的方式使用JSON数据或其他媒体类型处理请求。
    可以使用DEFAULT_PARSER_CLASSES设置全局默认的解析器集。例如,以下设置将仅允许具有JSON内容的请求,而不是JSON或表单数据的默认值。
    全局
    REST_FRAMEWORK = {
      'DEFAULT_PARSER_CLASSES':(
          'rest_framework.parsers.JSONParser',
      )
    }
    
    局部
    ```python

    可以设置用于单个视图或视图集的解析器,使用APIView类视图

    from rest_framework.parsers import JSONParser from rest_framework.response import Response from rest_framework.views import APIView class ExampleView(APIView): “”” 可以接收JSON内容POST请求的视图 “”” parser_classes = (JSONParser,) def post(self,request,format=None):
      return Response({'data':request.data})
    

    或者,使用基于方法的视图的@api_view装饰器

    from rest_framework.decorators import api_view from rest_framework.decorators import parser_classes

@api_view([‘POST’]) @parser_classes((JSONParser,)) def example_view(request,format=None): “”” 可以接收JSON内容POST请求的视图 “”” return Response({‘data’:request.data})

<a name="6P912"></a>
### 3.4.2 Response
> REST framework提供了一个响应类Response,使用该类构造响应对象时,响应的具体数据内容会被转换(render渲染)成符合前端需求的类型。

```python
from rest_framework.response import Response
Response(data,status=None,template_name=None,headers=None,content_type=None)
# 参数说明:
data: 为响应准备的序列化处理后的数据
status: 状态码,默认200
template_name: 模板名称,如果使用HTMLRenderer时需指明
headers: 用于存放响应头信息的字典
content_type:响应数据的Content-Type,通常此参数无需传递,REST framework会根据前端所需类型数据来设置该参数。
  • data数据不是render处理之后的数据,只需传递python的内建类型数据即可,REST framework会使用renderer渲染器处理data
  • data不能是复杂结构的数据,如Django的模型类对象,对于这样的数据我们可以使用Serializer序列化器序列化处理后(转为Python字典类型)再传递给data参数

    3.5 状态码

    视图中使用纯数字的HTTP状态码并不总是那么容易被理解。而且如果错误代码出错,很容易被忽略。REST框架为status模块中的每个状态代码(如HTTP_400_BAD_REQUEST)提供更明确的标识符。使用它们来代替纯数字的HTTP状态码

  1. 信息告知 - 1XX

    HTTP_100_CONTINUE
    HTTP_101_SWITCHING_PROTOCOLS
    
  2. 成功 - 2XX

    HTTP_200_OK
    HTTP_201_CREATED
    HTTP_202_ACCEPTED
    HTTP_203_NOT_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
    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
    

    3.6 wrapping

    REST框架提供了两个可用于编写API视图的包装器(wrappers)

  • 用于基于函数视图的@api_view装饰器
  • 用于基于类视图的APIView类 ```python from rest_framework.decorators import api_view

@api_view([‘GET’,’POST’]) def snippet_list(request): pass

<a name="y6kxe"></a>
# 4 基于类的视图(CBV)
> REST framework提供了众多的通用视图基类与扩展类,以简化视图的编写。
> ![image.png](https://cdn.nlark.com/yuque/0/2021/png/207655/1616573782306-a0ccc96d-811d-4c50-8561-0f2cbbbdc32f.png#align=left&display=inline&height=294&margin=%5Bobject%20Object%5D&name=image.png&originHeight=512&originWidth=783&size=34797&status=done&style=none&width=450)

<a name="KEK4k"></a>
## 4.1 APIView
> APIView是DRF的基类,它支持GET POST PUT DELETE等请求类型,且各种类型的请求之间,有了更好的分离在进行dispatch分发之前,会对请求进行身份认证,权限检查,流量控制。APIView有两个:


![image.png](https://cdn.nlark.com/yuque/0/2021/png/207655/1616573997662-3935287c-005d-4e2f-bdf0-7b0a7eee86ee.png#align=left&display=inline&height=359&margin=%5Bobject%20Object%5D&name=image.png&originHeight=552&originWidth=800&size=359059&status=done&style=none&width=520)<br />rest_framework.views.APIView是REST framework提供的所有视图的基类,继承自Django的View父类。APIView与View的不同之处在于:

- 传入到视图方法中的是REST framework的Request对象,而不是Django的HttpRequest对象;
- 视图方法可以返回REST framework的Response对象,视图会为响应数据设置(render)符合前端要求的格式;
- 任何APIException异常都会被捕获到,并且处理成合适的响应信息;
- 在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制。
> 支持定义的属性是:

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

在APIView中仍以常规的类视图定义方法来实现get()、post()或者其他请求方式的方法。<br />🌰
```python
from rest_framework.views import APIView
from rest_framework.response import Response

# url(r'^books/$',views.BookListView.as_view())
class BookListView(APIView):
    def get(self,request):
        books = BookInfo.objects.all()
        serializer = BookInfoSerializer(books,many=True)
        return Repsonse(serializer.data)

4.2 mixins

使用基于类的视图的一个最大的好处就是我们可以灵活的选择各种View,使我们的开发更加简洁。mixins里面对应了ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin.

4.2.1 CreateModelMixin

创建视图扩展类,提供create(request,args,*kwargs)方法快速实现创建资源的视图,成功返回201状态码。如果序列化器对前端发送的数据验证失败,返回400错误。

  • 保存成功 perform_create(self,serializer)
  • 成功获取请求头的方法:get_success_headers(self,data)

    class CreateModelMixin:
      """
      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 {}
    

    4.2.2 ListModelMixin

    列表视图扩展类,提供list(request,args,*kwargs)方法快速实现列表视图,返回200状态码。 该Mixin的list方法会对数据进行过滤和分页

from rest_framework.mixins import ListModelMixin

class BookListView(ListModelMixin,GenericAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer

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

4.2.3 RetrieveModelMixin

详情视图扩展类,提供retrieve(request,args,*kwargs)方法,可以快速实现返回一个存在的数据对象。 如果存在,返回200,否则返回404

class RetrieveModelMixin:
    """
    Retrieve a model instance.
    """
    def retrieve(self, request, *args, **kwargs):
        # 获取对象,会检查对象的权限
        instance = self.get_object()
        # 序列化
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

🌰

class BookRetriveView(RetrieveAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookSerializer

    def get(self,request,pk):
        return self.retrieve(request)

4.2.4 UpdateModelMixin

更新视图扩展类,提供update(request,args,**kwargs)方法,可以快速实现更新一个存在的数据对象 同时也提供partial_update(request,args,**kwargs)方法,可以实现局部更新 成功返回200,序列化器校验数据失败时,返回400

class UpdateModelMixin:
    """
    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)

4.2.5 DestroyModelMixin

删除视图扩展类,提供destroy(request,args,*kwargs)方法,可以快速实现删除一个存在的数据对象 成功返回204,失败返回404

class DestroyModelMixin:
    """
    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()

4.3 使用generic

restframework提供了一组混合的generic视图,可以使我们的代码大大简化 GenericAPIView:继承自APIView,增加了对列表视图或者详情视图可能用到的通用支持方法。通常使用时,可搭配一个或者多个Mixin扩展类。支持定义的属性:

  • 列表视图与详情视图通用:
    • queryset 视图的查询集
    • serializer_class 视图使用的序列化器
  • 列表视图专用:
    • pagination_classes 分页控制类
    • filter_backends 过滤控制
  • 详情页视图专用:
    • lookup_field 查询单一数据库对象时使用的条件字段,默认为pk
    • lookup_url_kwarg 查询单一数据时URL钟的参数关键字名称,默认与look_field相同

提供的方法:
列表视图与详情视图通用

  • get_queryset 返回视图使用的查询集,是列表视图与详情视图获取数据的基础,默认返回queryset属性,可以重写

    def get_queryset(self):
      user = self.request.user
      return user.accounts.all()
    
  • get_serializer_class(self) 返回序列化器,默认返回serializer_class,可以重写

    def get_serializer_class(self):
      if self.request.user.is_staff:
          return FullAccountSerializer
      return BasicAccountSerializer
    
  • get_serialzer 返回序列化器对象,被其他视图或扩展类使用,如果我们再视图中想要获取序列化器对象,可以直接调用此方法

class UserInfoView(GenericAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    lookup_field = 'pk'

    def get(self, request, pk):
        obj = self.get_object()
        us = UserSerializer(instance=obj)
        return Response(us.data)


class UserQueryView(GenericAPIView):
    # 查询结果集
    queryset = User.objects.all()
    serializer_class = UserSerializer

    def get(self, request, name):
        data = self.get_queryset()
        data = data.filter(username=name)
        print(data)
        us = UserSerializer(instance=data, many=True)
        return Response(us.data)

5 认证、权限和限制

权限确定是否能访问某个接口,限制确定你访问某个接口的频率

5.1 认证

身份验证是将传入请求与一组标识凭据(列入请求来自用户或其签名的令牌)相关联的机制。然后权限和限制组件决定是否拒绝这个请求。认证本身不会允许或拒绝传入的请求,它只是简单识别请求携带的凭证。

REST framework提供了一些开箱即用的身份验证方案,并且还允许你实现自定义的方案。

# 基于用户名和密码的认证
class BasicAuthentication(BaseAuthentication):
# 基于Session认证
class SessionAuthentication(BaseAuthentication):
# 基于Token的认证
class TokenAuthentication(BaseAuthentication):
# 基于远程用户的认证(专用用户管理服务器)
class RemoteUserAuthentication(BaseAuthentication):

5.1.1 自定义认证

要实现自定义的认证方案,要继承BaseAuthentication类并且重写.authenticate(self,request)方法。如果认证成功,该方法应返回(user,auth)的二元元组,否则返回None 在某些情况下,可能不想返回None,而是希望从.authenticate()方法抛出AuthenticationFailed异常 在App下,创建authentications.py,然后自定义认证类

# tokens.py
from itsdangerous import URLSafeTimedSerializer as utsr
import base64
from django.conf import settings


class Token:
    def __init__(self, security_key):
        self.security_key = security_key
        self.salt = base64.encodebytes(security_key.encode('utf8')) # 将字符串转为ascii

    def generate_validate_token(self, username):
        serializer = utsr(self.security_key)
        return serializer.dumps(username, self.salt)

    def confirm_validate_token(self, token, expiration=3600):
        serializer = utsr(self.security_key)
        return serializer.loads(token, salt=self.salt, max_age=expiration)

    def remove_validate_token(self, token):
        serializer = utsr(self.security_key)
        print(serializer.loads(token, salt=self.salt))
        return serializer.loads(token, salt=self.salt)


token_confirm = Token(settings.SECRET_KEY) # 定义为全局变量
# views.py
class UserToken(GenericAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    def get(self, request):
        # 产生token
        user = User.objects.first()
        # 使用用户id生成token
        token = token_confirm.generate_validate_token(user.id)
        # 把token返回给客户端
        return Response({'token': token})
# authentications.py
import itsdangerous
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed

from drf.models import User
from myApp.email_token import token_confirm


class MyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # 1. 获取token
        # token可以从请求的get参数中获取
        token = request.query_params.get('token')
        try:
            uid = token_confirm.confirm_validate_token(token, expiration=30)
        except itsdangerous.exc.SignatureExpired as e:  # token 过期
            raise AuthenticationFailed('token已过期')
        except:
            return None  # 认证不通过
        # 如果获取到uid
        # 查询数据库,获取用户信息
        try:
            user = User.objects.get(pk=uid)
        except:
            print('数据库访问错误')
            return None
        # 如果找到了用户,认证成功
        return (user, None) # 第二值,request auth可以获取

5.1.2 全局级别认证配置

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES':(
      # 应用名.模块名.认证类名
      'drf.authentications.MyAuthentication'
    ),
}

5.1.3 视图级别认证配置

class UserInfoView(GenericAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    lookup_field = 'pk'
    authentication_classes = (MyAuthentication,)
    def get(self, request, pk):
        obj = self.get_object()
        us = UserSerializer(instance=obj)
        return Response(us.data)

5.2 权限

5.2.1 权限控制

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

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

rest_framework也提供了相应的支持,源码如下:

class BasePermission(metaclass=BasePermissionMetaclass):
    """
    A base class from which all permission classes should inherit.
    """
    # 判断是否对视图有权限
    def has_permission(self, request, view):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return True
    # 判断是否对某个视图有权限
    def has_object_permission(self, request, view, obj):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return True

系统内置的权限

  • AllowAny 允许所有用户
  • IsAuthenticated 仅通过认证的用户
  • IsAdminUser 仅管理员用户
  • IsAuthenticatedOrReadOnly 认证的用户可以完全操作,否则只能get读取

    代码实现

    如果想编写一个权限的控制

  • 首先,我们要在app目录下新建一个文件比如是permission.py,然后自定义一个权限类 ```python from rest_framework.permissions import BasePermission

class SuperPermission(BasePermission):

# 重写里面的has_permission函数
def has_permission(self, request, view):
    # 判断当前用户是不是超管
    return request.user.is_superuser

class StaffPermission(BasePermission): def has_permission(self, request, view): return request.user.is_staff


- 对应的Serializer
```python
from django.contrib.auth.models import User
from rest_framework import serializers
from .models import Book

class UsersSerializer(serialzers.ModelSerializer):
    class Meta:
        model = User
        fields = ("id","username","email")
  • 视图级别权限认证

    class UsersAPI(ListAPIView):
      queryset = User.objects.all()
      # 指定使用的序列化器
      serializer_class = UserSerializer
      authentication_classes = (MyAuthentication,)
      # 权限控制
      permission_classes = (SuperPermission, StaffPermission)
    
      def list(self, request, *args, **kwargs):
          user = request.user
          if not isinstance(user, User):
              return Response({
                  "code": 1,
                  "msg": '用户名或密码错误'
              })
          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)
    

    全局级别的权限认证

    REST_FRAMEWORK = {
      'DEFAULT_PERMISSION_CLASSES': [
          'drf.permission.SuperPermission',
          'drf.permission.StaffPermission'
      ],
    }
    

    5.3 节流

    节流类似于权限,用于控制客户端可以对API发出的请求的速率

5.3.1 自定义类继承内置类

from rest_framework.throttling import SimpleRateThrottle

class VisitThrottle(SimpleRateThrottle):
    # 转换频率每分钟5次,转换频率 = num/duration,其中duration可以是s/m/h/d
    rate = '5/m'
    scope = 'visitor'

    # 返回一个唯一标识用以区分不同的用户
    def get_cache_key(self, request, view):
        # return self.get_ident(request)  # 返回用户ip
        res = not(request.user and request.user.id)
        return None if res == False else res

5.3.2 局部限制

# views.py
# 获取当前用户创建的书籍,要包括用户信息和他所有相关书籍的数据
class XXXAPIView(ListAPIView):
    throttle_classes = (VisitThrottle,)
    ...

5.3.3 全局限制

# 
REST_FRAMEWORK = {
    # 对全局进行限制
    'DEFAULT_THROTTLE_CLASSES': ['drf.mythrottle.VisitThrottle']
    'DEFAULT_THROTTLE_RATES':{
        'vistor': '3/m',
        'anon': '10/day', # 匿名用户
    },
}

DEFAULT_THROTTLE_RATES 可以使用second,minute,hour或day来指明周期。

5.3.4 可选限流类

  1. AnonRateThrottle
    1. 限制所有匿名未认证用户,使用IP区分用户
    2. 使用DEFAULT_THROTTLE_RATES[‘anon’]来设置频次
  2. UserRateThrottle
    1. 限制认证用户,使用User id来区分
    2. 使用DEFAULT_THROTTLE_RATES[‘user’]来设置频次
  3. ScopedRateThrottle
    1. 限制用户对于每个视图的访问频次,使用ip或user id

🌰

class ContactListView(APIView):
    throttle_scope = 'contacts'
    ...
class ContactDetailView(APIView):
    throttle_scope = 'contacts'
     ...
class UploadView(APIView):
    throttle_scope = 'uploads'
    ...
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES':('rest_framework.throttling.ScopedRateThrottle',),
    'DEFAULT_THROTTLE_RATES': {
        'contacts':'1000/day',
        'uploads':'20/day'
    }
}

6 分页

REST framework提供了分页的支持 可以在配置文件中设置全局的分页方式,如:

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,  # 每页数目
}

也可以通过自定义Pagination类,来为视图添加不同分页行为。在视图中通过pagination_class属性来指明。

class LargeResultSetPagination(PageNumberPagination):
    page_size = 1000
    page_size_query_param = 'page_size'
    max_page_size = 10000
    # 重写返回结果
    def get_paginated_response(self, data):
        return Response(OrderedDict([
            ('count', self.page.paginator.count),
            ('results', data)
        ]))

class BookDetailView(RetrieveAPIView):
    queryset = Bookinfo.objects.all()
    serializer_class = BookInfoSerializer
    pagination_class = LargeResultSetPagination

注意:如果在视图内关闭分页功能,只需在视图内设置

pagination_class = None
class BookListView(GenericAPIView):
    queryset = Bookinfo
    serializer_class = BookInfoSerializer
    pagination_class = PageNumberPagination

    def get(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)

6.1 可选分页器

6.1.1 PageNumberPagination

前端访问网址形式:

GET http://api.example.org/books/?page=4

可以在子类中定义的属性:

  • page_size 每页数目
  • page_query_param 前端发送的页数关键字名,默认为page
  • page_size_query_param 前端发送的每页数目关键字名,默认为None
  • max_page_size 前端最多能设置的每页数量 ```python from rest_framework.pagination import PageNumberPagination

class StandardPageNumberPagination(PageNumberPagination): page_size_query_param = ‘page_size’ max_page_size = 10 class BookListView(ListAPIView): queryset = BookInfo.objects.all().order_by(id) serializer_class = BookInfoSerializer pagination_class = StandardPageNumberPagination

<a name="zYjJ1"></a>
### 6.1.2 LimitOffestPagination
> 前端访问网址形式:

```python
GET http://api.example.org/books/?limit=100&offset=400

可以在子类中定义的属性

  • default_limit 默认限制,默认值与pgae_size设置一致
  • limit_query_param limit参数名,默认limit
  • offset_query_param offset参数名,默认offset
  • max_limit 最大limit限制,默认None ```python from rest_framework.pagination import LimitOffsetPagination

class BookListView(ListAPIView): queryset = BookInfo.objects.all().order_by(id) serializer_class = BookInfoSerializer pagination_class = LimitOffsetPagination

<a name="vUm52"></a>
# 7 过滤器
> 对于列表数据可能需要根据字段进行过滤,可以通过添加django-filter扩展来增强支持。

<a name="xg16T"></a>
## 7.1 安装
```python
pip3 install django-filter

django-filters支持的python和django版本:

  • Python 3.5,3.6,3.7,3.8
  • Django 1.11,2.0,2.1,2.2,3.0
  • DRF:3.10+

7.2 配置

INSTALLED_APPS = [
    ...
    'django_filters',
]
REST_FRAMEWORK = {
    ...
    'DEFAULT_FILTER_BACKENDS':('django_filters.rest_framework.DjangoFilterBackend')
}

7.3 使用

只能and,需要or自己写

class BookListView(ListAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    filter_fields = ('btitle','bread')
# 判断相等
# 127.0.0.1:8000/books/?btitle=xxx

7.4 自定义过滤类/自定义过滤方法

import django_filters
from django_filters import rest_framework as filters

from drf.models import Bookinfo

class BookFilter(django_filters.FilterSet):
    image = filters.CharFilter('bimage', method='filter_empty_string')

    class Meta:
        # filed_name='bread' 模型中的字段名;lookup_expr是运算,gte表示>=
        min_read = filters.NumberFilter(field_name='bread', lookup_expr='gte')
        max_read = filters.NumberFilter(field_name='bread', lookup_expr='let')
        model = Bookinfo
        fields = {
            'btitle': ['icontains'],  # 键是字段名,列表里是查询进行运算
            'bcomment': ['lt', 'gt', 'in'],
            'bpub_date': ['exact', 'gt', 'year__lt', 'year__gt'],
            'bimage': ['isnull']
        }

    def filter_empty_string(self, queryset, name, value):
        return queryset.filter(bimage='')
# 127.0.0.1:8000/books/?btitle__icontains=笑
# 127.0.0.1:8000/books/?min_read=30&max_read=80
# 127.0.0.1:8000/books/?bcomment__in=20,30
# 127.0.0.1:8000/books/?path=''


class IndexView(ListAPPView):
    serializer_class = BookInfoSerializer
    queryset = Bookinfo.objects.all()
    filter_class = BookFilter # 指定过滤器
  • field_name 数据库中字段名
  • lookup_expr 操作(和django的orm运算符一样)

    8 接口自动生成文档

    REST framework可以自动帮助我们生成接口文档 接口文档以网页的方式呈现 自动接口文档能生成的是继承自APIView及其子类的视图

8.1 安装依赖

REST framework生成接口文档需要coreapi库的支持。

pip3 install coreapi

8.2 配置

REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS':'rest_framework.schemas.coreapi.AutoSchema'
}

8.3 设置接口文档访问路径

在总路由中添加接口文档路径 文档路由对应的视图配置为rest_framework.documentation.include_docs_urls 参数title为接口文档网站的标题

from rest_framework.documentation import include_docs_urls
urlpatterns = [
    ...
    re_path(r'^docs/',include_docs_urls(title='My API title'))
]

8.4 文档描述说明的定义位置

8.4.1 单一方法的视图,可直接使用类视图的文档字符串,如

class BookListView(generics.ListAPIView):
    """
    返回所有图书信息.
    """

8.4.2 包含多个方法的视图,在类视图的文档字符串中,分开方法定义

class BookListCreateView(generics.ListCreateAPIView):
    """
    get:
    返回所有图书信息.

    post:
    新建图书.
    """

8.4.3 对于视图集ViewSet,仍在类视图的文档字符串中分开定义,但是应使用action名称区分

class BookInfoViewSet(mixins.ListModelMixin,mixins.RetrieveModelMixin,GenericViewSet):
    """
    list:
    返回图书列表数据

    retrieve:
    返回图书详情数据

    lastest:
    返回最新的图书数据

    read:
    修改图书的阅读量
    """

image.png

8.4.4 说明

1)视图表ViewSet中的retrieve名称,在接口文档网站中叫做read

2)参数的Description需要在模型类或序列化器类的字段中以help_text选项定义,如:

class BookInfo(models.Model):
    ...
    bread = models.IntegerField(default=0,verbose_name='阅读量',help_text='阅读量')

class BookReadSerializer(serializers.ModelSerializer):
    class Meta:
        model = BookInfo
        fields = ('bread',)
        extra_kwargs = {
            'bread':{
                'required':True,
                'help_text':'阅读量'
            }
        }

9 问题汇总

  • 静态资源404 ```python

    在项目执行

    python manage.py collectstatic

    settings.py

    import os

STATIC_URL = ‘/static/‘ STATIC_ROOT = os.path.abspath(os.path.join(BASE_DIR, ‘static’))

普通文件

STATICFILES_DIRS = [

os.path.join(BASE_DIR, ‘static’)

]

```