Django REST framework 一个强大灵活的 Django 工具包,提供了便捷的 REST API 开发框架
我们用传统的 django 也可以实现REST风格的 api ,但是顶不住 Django REST framework 实现起来太简单了

什么REST API风格

主要需要注意这几个:

  • api部署的域名, 主域名或者专有域名
  • 版本, 通过url地址或者请求头accept
  • 路径, 只能有名词, 不能有动词
  • http请求动词, get, post, update, delete
  • 状态, 200, 201, 204, 400, 401, 403, 404
  • 返回json格式数据
  • 例子(仅作参考):

    响应状态码(成功) 响应体(成功)

    简单使用流程

    在我看来, Django RST framework 的主要工作就是对传统 django 中的 view form 在进行了通用性设计。由于使用REST API,我们可以统一使用一样的请求方式,请求参数和请求体,而响应体和状态码也是确定。所以 Django RST framework 的复用性极高,而且 Django RST framework 提供了从简单到复杂的封装类,我们只需要直接使用这些类或继承这些类重写部分方法,就可以享受到便利性和可扩展性。
    为了显示 Django RST framework 简单易用,用展示封装程度比较高的类实现一个五个基本功能(get一个、get全部、post创建、put修改单个、delete删除单个)

    安装 Django RST framework

    $pip install djangorestframework
    

    修改setting文件
    settings.INSTALLED_APPS中添加rest_framework

    注:添加这个app可以为我们提供测试页面, 假如是使用postman等工具进行测试的话可以不用添加

    创建model

    from django.db import models
    class Author(models.Model):
        aid = models.AutoField(primary_key=True)
        name = models.CharField(max_length=32, verbose_name="姓名")
        phone = models.CharField(max_length=11, verbose_name="手机号码")
        address = models.CharField(max_length=63, verbose_name="地址")
        def __str__(self):
            return self.name
    

    定义序列化器
    在app目录下创建一个serializers.py,在里面定义序列化器:

    from rest_framework.serializers import ModelSerializer
    from rest_test.models import Author
    class AuthorSerialize(ModelSerializer):
        class Meta:
            model = Author
            fields = "__all__"
    
    from rest_framework.viewsets import ModelViewSet
    from rest_test.serializers import AuthorSerialize
    from rest_test.models import Author
    class AuthorModelViewSet(ModelViewSet):
        queryset = Author.objects.all()
        serializer_class = AuthorSerialize
    from django.urls import path
    from rest_test import views
    from rest_framework.routers import SimpleRouter
    urlpatterns = [
    	path('admin/', admin.site.urls),
    router = SimpleRouter()
    # 添加自己的url
    router.register('authors', views.AuthorModelViewSet, basename="authors")
    urlpatterns += router.urls
    

    序列化器的主要作用就是进行数据转换,即序列化(python的数据类型转换为json等数据类型)和反序列化(json等数据类型转换为python的数据类型)
    在反序列化的过程中我们还可以手动验证数据是否合法, 可以与model相结合,完成对数据库的操作
    所以说,序列化器和djangoForm非常像,所以假如会djangoForm的话,应该很好理解

    普通的序列化器

    在一定程度上可以类比dango.forms.Form

    定义起来非常简单,字段名称对应的是model中的字段名,字段类型和model中的字段有一定的对应关系,参数是一些约束条件和描述信息, 见: 字段和参数

    from rest_framework import serializers
    class AuthorSerialize(serializers.Serializer):
        aid = serializers.IntegerField(read_only=True,label="作者id")
        name = serializers.CharField(max_length=32, label="姓名")
        phone = serializers.CharField(min_length=11, max_length=11, label="手机号码")
        address = serializers.CharField(max_length=63, label="地址")
    

    字段和参数

    这里的参数指的是字段自带的参数,不是像required这样都可以用的参数
    仅列出常用的

    全部字段都是rest_framework.serializers下的

    序列化操作

    书接上回,我们已经在serializers.py中定义了一个AuthorSerialize,下面就利用该序列化器进行序列化,即python的转换为json等数据。

    序列化一个

  • 导入AuthorSerializeAuthor模型
  • 使用get获取Author的数据
  • 将数据传给AuthorSerializeinstance参数
  • 使用.data属性(是一个静态属性)获得序列化后的数据
  • 注意:假如你的数据库中没有数据,应该提前将数据创建好,可以用SQL命令也可以用django ORM或借助其他工具

    使用django shell执行命令:

    $ python manage.py shell
    Python 3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    (InteractiveConsole)
    >>> from rest_test.serializers import AuthorSerialize
    >>> from rest_test.models import Author
    >>> a = Author.objects.get(pk=4)
    >>> t = AuthorSerialize(instance=a)
    >>> t.data
    {'aid': 4, 'name': '张三', 'phone': '1388888888', 'address': '广东省广州市xxxxx'}
    

    序列化多个

    序列化器一般是不能序列化多个数据的,需要我们指定参数many=True。同样的,我们先使用django shell执行命令:

    $ python manage.py shell
    Python 3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    (InteractiveConsole)
    >>> from rest_test.serializers import AuthorSerialize
    >>> from rest_test.models import Author
    >>> a = Author.objects.all()
    >>> t = AuthorSerialize(instance=a, many=True)
    >>> t.data
    [OrderedDict([('aid', 4), ('name', '张三'), ('phone', '138888888888'), ('address', '广东省广州市xxxx')]),
    OrderedDict([('aid', 5), ('name', '李四'), ('phone', '1388888888'), ('address', '广东省广州市xxxxx')]), 
    OrderedDict([('aid', 6),('name', '王五'), ('phone', '1388888888'), ('address', '广东省广州市xxxxx')])]
    

    可以看到,data的值是一个OrderedDict数据类型

    外键序列化

    由于外键在序列化时不知道是使用主键、__str__或整个主键的数据中的一个,所以需要我们手动指定不同的字段实现。
    假如我们添加了一个model:

    class Author(models.Model):
        aid = models.AutoField(primary_key=True)
        name = models.CharField(max_length=32, verbose_name="姓名")
        phone = models.CharField(max_length=11, verbose_name="手机号码")
        address = models.CharField(max_length=63, verbose_name="地址")
        def __str__(self):
            return self.name
    class Books(models.Model):
        bid = models.AutoField(primary_key=True)
        name = models.CharField(max_length=32, verbose_name="书名")
        description = models.CharField(max_length=128, verbose_name="书籍描述")
        author = models.ForeignKey(Author, on_delete=models.CASCADE)
        def __str__(self):
            return f"{self.author.name}: 《{self.name}》"
    

    子表序列化

    子表序列化比较简单,因为字段定义子表哪边,所以可以通过字段,只需要确定返回主表的哪些值即可。

    返回外键的主键PrimaryKeyRelatedField
    定义序列化器:

        class BookSerialize(serializers.Serializer):
            bid = serializers.IntegerField(read_only=True)
            name = serializers.CharField(max_length=32, label="书名")
            description = serializers.CharField(max_length=128, label="书籍描述")
            author = serializers.PrimaryKeyRelatedField(read_only=True)
    

    PrimaryKeyRelatedField必须指定read_only为True,或指定queryset=Books.objects.all(),否则会报错

    使用django shell测试:

    $ python manage.py shell
    Python 3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    (InteractiveConsole)
    >>> from rest_test.serializers import BookSerialize
    >>> from rest_test.models import Books
    >>> b = Books.objects.all()
    >>> s = BookSerialize(instance=b, many=True)
    >>> s.data
    [OrderedDict([('bid', 1), ('name', 'book1'), ('description', 'very good'), ('author', 4)]),
    OrderedDict([('bid', 2), ('name', 'book2'), ('description', '好书推荐'), ('author', 4)])]
    

    返回外键的__str__方法StringRelatedField
    修改author的字段类型,让它返回的是主表的__str__方法:

    class BookSerialize(serializers.Serializer):
        bid = serializers.IntegerField(read_only=True)
        name = serializers.CharField(max_length=32, label="书名")
        description = serializers.CharField(max_length=128, label="书籍描述")
        author = serializers.StringRelatedField(read_only=True)
    
    $ python manage.py shell
    Python 3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    (InteractiveConsole)
    >>> from rest_test.serializers import BookSerialize
    >>> from rest_test.models import Books
    >>> b = Books.objects.all()
    >>> s = BookSerialize(instance=b, many=True)
    >>> s.data
    [OrderedDict([('bid', 1), ('name', 'book1'), ('description', 'very good'), ('author', '张三')]), 
    OrderedDict([('bid', 2), ('name', 'book2'), ('description', '好书推荐'), ('author', '张三')])]
    

    返回整个主表的数据
    只需要字段的值改为主表的序列化器即可:

    class AuthorSerialize(serializers.Serializer):
        aid = serializers.IntegerField(read_only=True)
        name = serializers.CharField(max_length=32, label="姓名")
        phone = serializers.CharField(min_length=11, max_length=11, label="手机号码")
        address = serializers.CharField(max_length=63, label="地址")
    class BookSerialize(serializers.Serializer):
        bid = serializers.IntegerField(read_only=True)
        name = serializers.CharField(max_length=32, label="书名")
        description = serializers.CharField(max_length=128, label="书籍描述")
        author = AuthorSerialize(read_only=True)
    
    $ python manage.py shell
    Python 3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    (InteractiveConsole)
    >>> from rest_test.serializers import AuthorSerialize, BookSerialize
    >>> from rest_test.models import Author, Books
    >>> b = Books.objects.all()
    >>> s = BookSerialize(instance=b, many=True)
    >>> s.data
    [OrderedDict([('bid', 1), ('name', 'book1'), ('description', 'very good'), 
    ('author', OrderedDict([('aid', 4), ('name', '张三'), ('phone', '138888888888'), ('address', '广东省广州市xxxxx')]))]), 
    OrderedDict([('bid', 2), ('name', 'book2'), ('description', '好书推荐'), 
    ('author', OrderedDict([('aid', 4), ('name', '张三'), ('phone', '138888888888'), ('address', '广东省广州市xxxxx')]))])]
    

    主表序列化

    由于主表不能直接访问子表,但是我们知道django为我们提供了表名(小写)_set管理器,这样我们就可以通过该管理器访问子表了。

    假如指定了related_name参数的话,该管理器的名称就是related_name的值
    如可以改成这样:author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="author")
    另,关于管理器,可以查看django的文档:“反向” 关联管理器

    为了演示,使用默认的管理器

    与子表类似,我们需要指定返回什么值(__str__或主键),使用的是相同的字段,但是要指定many参数!!

    __str__ StringRelatedField

    注意many参数

    序列化器:

    class AuthorSerialize(serializers.Serializer):
        aid = serializers.IntegerField(read_only=True)
        name = serializers.CharField(max_length=32, label="姓名")
        phone = serializers.CharField(min_length=11, max_length=11, label="手机号码")
        address = serializers.CharField(max_length=63, label="地址")
        books_set = serializers.StringRelatedField(read_only=True, many=True)
    
    $ python manage.py shell
    Python 3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    (InteractiveConsole)
    >>> from rest_test.serializers import AuthorSerialize
    >>> from rest_test.models import Author
    >>> a = Author.objects.get(pk=4)
    >>> t = AuthorSerialize(instance=a)
    >>> t.data
    {'aid': 4, 'name': '张三', 'phone': '138888888888', 'address': '广东省广州市xxxxx', 'books_set': ['张三: 《book1》', '张三: 《book2》']}
    

    主键PrimaryKeyRelatedField

    注意many参数

    序列化器:

    class AuthorSerialize(serializers.Serializer):
        aid = serializers.IntegerField(read_only=True)
        name = serializers.CharField(max_length=32, label="姓名")
        phone = serializers.CharField(min_length=11, max_length=11, label="手机号码")
        address = serializers.CharField(max_length=63, label="地址")
        books_set = serializers.PrimaryKeyRelatedField(read_only=True,many=True)
    
    $ python manage.py shell
    Python 3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    (InteractiveConsole)
    >>> from rest_test.serializers import AuthorSerialize
    >>> from rest_test.models import Author
    >>> a = Author.objects.get(pk=4)
    >>> t = AuthorSerialize(instance=a)
    >>> t.data
    {'aid': 4, 'name': '张三', 'phone': '138888888888', 'address': '广东省广州市xxxxx', 'books_set': [1, 2]}
    

    反序列化操作

    上面演示了如何将model序列化成 字符串或OrderedDict,而反序列化化就是将json等数据,变成序列化器中的字段的数据类型,就好比我们使用form处理数据一样。

    此部分还不涉及将数据保存到数据库

  • 将数据传入到序列化器的data参数中
  • 调用返回对象的is_valid()方法
  • 由于还未讲到视图,所以先用一个字典数据代替从浏览器传过来的数据。

    $python manage.py shell
    Python 3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    (InteractiveConsole)
    >>> from rest_test.serializers import AuthorSerialize, BookSerialize
    >>> from rest_test.models import Author, Books
    >>> data = {
    ...     "name": "龙傲天",
    ...     "phone": "18888888888",
    ...     "address": "xxx省xx市",
    ... }
    >>> result = AuthorSerialize(data=data)
    >>> result.is_valid(raise_exception=True)
    # ################ 这里故意用一个错误的数据 #############################
    >>> data = {
    ...     "name": "龙傲天",
    ...     "phone": "188888888889",
    ...     "address": "xxx省xx市",
    ... }
    >>> result = AuthorSerialize(data=data)
    >>> result.is_valid(raise_exception=True)
    rest_framework.exceptions.ValidationError: {'phone': [ErrorDetail(string='Ensure this field has no more than 11 characters.', code='max_length')]}
    
  • is_validdjango formis_valid一样,可以检验数据是否符合我们在序列化器中定义的一样,检验成功是返回True,否则返回False
  • 假如指定raise_exceptionTrue,那么当数据不合法时,抛出异常,我们可以通过error_messages参数提示信息(见: 自定义错误信息)。
  • 假如不想要抛出异常,我们也可以通过.errors获取错误信息(这是一个OrderedDict的子类对象)如这里的result.errors
  • 利用序列化器进行创建和更新数据

    终于讲到如何将数据保存到数据库了。

    用过django ModelForm的都知道,只要将数据传入到form中,假如数据通过,就可以通过.save()方法将数据保存到数据库,指定instance参数可一个更新数据。而序列化器的使用方式样是这样,不过我们现在用的是叫较为低级的序列化器,需要我们自定义创建数据和更新数据的逻辑

    使用步骤:

  • 将数据传入到序列化器的data参数中
  • 调用返回对象的is_valid()方法
  • 为序列化器定义create方法
  • 调用save方法,执行创建数据的函数(会调用create函数)
  • 这里的create函数需要我们自定义,在序列化器中:

    class AuthorSerialize(serializers.Serializer):
        aid = serializers.IntegerField(read_only=True)
        name = serializers.CharField(max_length=32, label="姓名")
        phone = serializers.CharField(min_length=11, max_length=11, label="手机号码")
        address = serializers.CharField(max_length=63, label="地址")
        books_set = serializers.StringRelatedField(read_only=True, many=True)
        def create(self, validated_data):
            :param validated_data: 已经通过验证的数据
            :return: Author instance
            author = Author.objects.create(**validated_data)
            # 不要忘记返回数据了
            return author
    

    可以说非常简单,下面有django shell测试一下:

    $python manage.py shell
    Python 3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    (InteractiveConsole)
    >>> from rest_test.serializers import AuthorSerialize
    >>> data = {
    ...     "name": "龙傲天",
    ...     "phone": "18888888888",
    ...     "address": "xxx省xx市",
    ... }
    >>> result = AuthorSerialize(data=data)
    >>> result.is_valid(raise_exception=True)
    >>> result.save()
    <Author: 龙傲天>
    

    用PyCharm自带的Database工具,我们可以看到的确已经保存到了数据库中:

    更新数据(全部)

    PUT请求方式是更新全部数据

    使用步骤:

  • 将数据传入到序列化器的data参数中, 要更新的数据传入instance参数
  • 调用返回对象的is_valid()方法
  • 为序列化器定义update方法
  • 调用save方法,执行创建数据的函数(会调用update函数)
  • 只要序列化器中同时传入datainstance参数,它就会调用更新的方法(update)

    注意:data参数传入数据,instance参数传入待更新的model对象

    class AuthorSerialize(serializers.Serializer):
        aid = serializers.IntegerField(read_only=True)
        name = serializers.CharField(max_length=32, label="姓名")
        phone = serializers.CharField(min_length=11, max_length=11, label="手机号码")
        address = serializers.CharField(max_length=63, label="地址")
        books_set = serializers.StringRelatedField(read_only=True, many=True)
        def update(self, instance, validated_data):
            由于传入的不是QuerySet对象,所以不能用update方法
            :param instance: Author对象
            :param validated_data: 已经验证的数据
            :return: instance
            instance.name = validated_data.get("name")
            instance.phone = validated_data.get("phone")
            instance.address = validated_data.get("address")
            instance.save()  # 不要忘记保存了
            return instance
    
    $ python manage.py shell
    Python 3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    (InteractiveConsole)
    >>> from rest_test.serializers import AuthorSerialize
    >>> from rest_test.models import Author
    >>> author = Author.objects.get(pk=10)
    >>> data = {
    ...     "name": "龙傲天",
    ...     "phone": "18888888888",
    ...     "address": "广东省广州市",
    ... }
    >>> result = AuthorSerialize(data=data, instance=author)
    >>> result.is_valid(raise_exception=True)
    >>> result.save()
    <Author: 龙傲天>
    

    更新数据(部分)

    PATCH请求方式是更新部分数据

    上面我们只更新了地址的信息,却把其它不用改的信息也一并穿了进去,这显然不是我们预期的,但由于序列化器的字段的required默认为True,除非我们设置read_only=True或指定一个默认值,否则会报错,但设置了这些又会影响到我们正常创建数据的验证。
    所以,序列化器提供了一个参数:partial用来说明这是更新部分信息的。在使用时和上面的一样,只是多了个参数而已。不过我们需要修改一下update函数, 因为值可能为None, 会报错,可以指定get的默认值,也可以用反射的方法:

    class AuthorSerialize(serializers.Serializer): aid = serializers.IntegerField(read_only=True) name = serializers.CharField(max_length=32, label="姓名") phone = serializers.CharField(min_length=11, max_length=11, label="手机号码") address = serializers.CharField(max_length=63, label="地址") books_set = serializers.StringRelatedField(read_only=True, many=True) def update(self, instance, validated_data): :param instance: Author对象 :param validated_data: 已经验证的数据 :return: instance for field in validated_data: if not hasattr(instance, field): continue setattr(instance, field, validated_data.get(field)) instance.save() # 不要忘记保存了 return instance

    来试一下吧:

    $ python manage.py shell
    Python 3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    (InteractiveConsole)
    >>> from rest_test.serializers import AuthorSerialize
    >>> from rest_test.models import Author
    >>> data = {
    ...     "address": "广东省广州市xxxxx",
    ... }
    >>> author = Author.objects.get(pk=10)
    >>> result = AuthorSerialize(data=data, instance=author, partial=True)
    >>> result.is_valid(raise_exception=True)
    >>> result.save()
    <Author: 龙傲天>
    

    Model的序列化器

    DRF有一个model序列化器: ModelSerializer, 其在一定程度上与dango.forms.ModelForm十分相似
    在上面使用的例子就用到了该序列化器

    说到底,它就是将干了这几件事:

  • 我们的model字段根据对应关系转化为serializers中的字段
  • 将model字段的约束条件变为serializers字段的约束条件
  • 内置了create方法和update方法
  • 简直是简简单单:

    class AuthorModelSerialize(serializers.ModelSerializer):
        class Meta:
            model = Author
            fields = "__all__"
    

    Meta类

    从例子中看出,Meta类是关键,它决定着 要序列化的字段、用哪个模型、错误信息、约束条件等重要的参数。
    下面我们看看Meta类可以指定什么内容:

    还是那句话,真的和ModelForm十分相似

  • model
    指定用哪个Model
  • fields
    使用模型中的哪些字段,推荐利用元组显式指定,但可以将值指定为__all__(表示全部字段)
    不能和exclude一起使用,否则报错:Cannot set both 'fields' and 'exclude' options on serializer xxx.
  • exclude
    不使用模型中的哪些字段
    不能和fields一起使用,否则报错:Cannot set both 'fields' and 'exclude' options on serializer xxx.
  • depth
    指定关联深度,是一个数字
    注意,关联字段默认返回的是主键,即rest_framework.serializers.PrimaryKeyRelatedField
  • read_only_fields
    只读字段,主键默认只读
  • extra_kwargs
    指定额外的参数,该参数可以传给字段
  • class AuthorModelSerialize(serializers.ModelSerializer):
        class Meta:
            model = Author      # 指定使用Author这个模型
            # 指定字段,注意:books_set
            fields = ("aid", "name", "phone", "address", "books_set")
            # 不使用模型中的哪些字段
            # 不能一起使用,这里注释掉
            # exclude = ("phone", )
            depth = 1       # 关联深度为1
            # 设置只读字段
            read_only_fields = ("aid", "books_set")
            # 设置额外参数,该参数是给字段的
            # 注意格式
            extra_kwargs = {
                "name": {
                    "read_only": True
    

    我们同样用django shell查看:

    $ python manage.py shell
    Python 3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    (InteractiveConsole)
    >>> from rest_test.serializers import AuthorModelSerialize
    >>> from rest_test.models import Author
    >>> a = Author.objects.all()
    >>> res = AuthorModelSerialize(instance=a, many=True)
    AuthorModelSerialize(instance=<QuerySet [<Author: 张三>, <Author: 李四>, <Author: 王五>, <Author: 龙傲天>]>, many=True):aid = IntegerField(read_only=True) name = CharField(label='姓名', read_only=True) address = CharField(label='地址', max_length=63) books_set = NestedSerializer(many=True, read_only=True): bid = IntegerField(read_only=True) name = CharField(label='书名', max_length=32) description = CharField(label='书籍描述', max_length=128) author = PrimaryKeyRelatedField(queryset=Author.objects.all())
    

    extra_kwargs

    由于ModelSerializer直接用model的字段类型,肯定不能很好的做好条件约束
    因此DRF提供了extra_kwargs属性, 并且错误信息也通过该参数进行自定制

    class AuthorModelSerialize(serializers.ModelSerializer):
        class Meta:
            model = Author  # 指定使用Author这个模型
            # 指定字段,注意:books_set
            fields = ("aid", "name", "phone", "address", "books_set")
            extra_kwargs = {
                "phone": {
                    "min_length": 11,
                    "max_length": 11
    

    像普通Serialize那样编写

    我们可以自定义一些额外的字段或不用extra_kwargs属性而直接model的字段进行定义:

    class AuthorModelSerialize(serializers.ModelSerializer):
        phone = serializers.CharField(min_length=11, max_length=11, label="手机号码")
    	# write_only只反序列化,不入库
        note = serializers.CharField(max_length=128, label="额外的字段,不入库", write_only=True)    
        class Meta:
            model = Author  # 指定使用Author这个模型
            # 指定字段,注意:books_set
            fields = ("aid", "name", "address", "books_set")
    

    指定外键返回类型

    外键默认返回主键,但通过serializer_related_field属性指定:

    class AuthorModelSerialize(serializers.ModelSerializer):
        serializer_related_field = serializers.StringRelatedField        
    	# 返回 __str__
        class Meta:
            model = Author
            fields = ("aid", "name", "address", "books_set")
    

    这部分内容也是和Model一样

    验证一个字段

    form一样,只要在序列化器重定义一个validate_xxx方法(xxx指的是字段名)即可验证单个字段,如下面的例子:

    class AuthorSerialize(serializers.Serializer):
        aid = serializers.IntegerField(read_only=True)
        name = serializers.CharField(max_length=32, label="姓名")
        phone = serializers.CharField(min_length=11, max_length=11, label="手机号码")
        address = serializers.CharField(max_length=63, label="地址")
        books_set = serializers.StringRelatedField(read_only=True, many=True)
        def validate_name(self, value:str):
            # 1. 判读
            if not value.startswith("lcz"):
                # 抛出ValidationError异常
                raise serializers.ValidationError("必须以lcz开头", "startError")
            # 2.返回结果
            return value
    

    ValidationError是一个异常,有两个参数detailcode,前者接收异常的描述信息,后者接收异常名(默认为"invalid"
    该方法调用is_valid时自动执行
    记得返回value

    验证一下:

    >>> data = {
    ...     "name": "xxx",
    ...     "phone": "12345678910",
    ...     "note": "test",
    ...     "address": "广东省广州市xxxxx1",
    ... }
    >>> result = AuthorSerialize(data=data)
    >>> result.is_valid()
    False
    >>> result.errors
    {'name': [ErrorDetail(string='必须以lcz开头', code='startError')]}
    

    验证多个字段

    需要重写validate方法:

    class AuthorSerialize(serializers.Serializer):
        aid = serializers.IntegerField(read_only=True)
        name = serializers.CharField(max_length=32, label="姓名")
        phone = serializers.CharField(min_length=11, max_length=11, label="手机号码")
        address = serializers.CharField(max_length=63, label="地址")
        books_set = serializers.StringRelatedField(read_only=True, many=True)
        def validate(self, attrs):
            :param attrs: 外界传入的需要校验的字典
            :return: attrs
            # 1. 判断name和address(随便举个例子)
            if attrs["name"].startswith("lcz") and attrs.startswith("广东省"):
                # 2.返回结果
                return attrs
            # 否则抛出异常
            raise serializers.ValidationError("只有lcz开头且地址是广东省的才能通过验证")
    

    自定义验证器

    使用验证器时,传入的数据已经转换为python的数据类型
    def check_bpub_date(date):
    	if date.year < 2015:
    		raise serializers.ValidationError("日期不能小于2015年")
    	return date
    通过validators参数指定
    def check_pub_date(date):
    	if date.year < 2015:
    		raise serializers.ValidationError("日期不能小于2015年")
    	return date
    class BookSerialize(serializers.Serializer):
    	pub_date = serializers.DateTimeField(validators=[check_pub_date]
    class AuthorSerialize(serializers.Serializer):
        aid = serializers.IntegerField(read_only=True)
        name = serializers.CharField(max_length=32, label="姓名")
        phone = serializers.CharField(min_length=11, max_length=11, label="手机号码",
                                      error_messages={"min_length": "号码太短了", "max_length": "号码太长了"})
        address = serializers.CharField(max_length=63, label="地址")
        books_set = serializers.StringRelatedField(read_only=True, many=True)
    

    和使用ValidationError的值定义为错误信息:

    def check_bpub_date(date):
        if date.year < 2015:
            raise serializers.ValidationError("日期不能小于2015年")
        return date
    

    Django REST framework的视图种类比序列化器还多,从一级视图到视图集,每一个都是在之前的视图基础上进行了封装,至于用哪个,全凭自己的喜好了。
    详情见下图:

    Django REST framework的视图都是用CBV的写法

    由于REST API的url是固定的,所以可以把视图分为 列表视图和详情视图,可能有点抽象,举个例子:

  • 列表视图:GET /books(获取所有的书籍);POST /books (创建书籍)
  • 详情视图:GET /books/{id}(获取单个书籍 );PUT /books/{id}(修改书籍);DELETE /books/{id} (删除书籍)
  • 可以感受出来了吧,对数据做增删改查需要两个路由5个函数, 因此我们可以将由于视图类分为 列表视图 和 详情视图两个视图类,不过我们可以利用 视图集 将视图类变为一个

    request和responses

    django中任何视图必然至少接收一个request,返回一个responseDjango REST framework也不例外。

    request

    DRFRequest类扩展了django默认的HttpRequest,它主要有两个属性:

  • Request.data
    得到请求体的数据(表单数据和JSON数据都行)
  • Request.query_params
    得到查询数据(任何HTTP方法类型可能包括查询参数,而不仅仅是GET请求
  • 除此外还有关于认证等属性,由于与django原来的request类似, 故这里不展开

    responses

    Response的签名:Response(data, status=None, template_name=None, headers=None, content_type=None)
    一般我们只需要用到前两个参数即可:

  • data: response的数列化数据
  • status: response的状态码。默认是200
  • template_name: HTMLRenderer 选择要使用的模板名称
  • headers: `响应头,是一个字典
  • content_type: response的内容类型。通常由渲染器自行设置
  • 常用状态码

    你可以在rest_framework.status中找到所有状态码

    一级视图APIView

    rest_framework.views.APIViewdjango.views.generic.base.View的子类,用起来和普通的传统的继承View的写法差不多

    列表视图的写法:

  • 定义一个类,继承APIView
  • 定义方法(get, post
  • request中获取数据
  • 利用 序列化器 序列化数据或检验数据入库
  • 返回(除非抛出异常,否则返回的都是data的数据)
  • from rest_framework.views import APIView
    from rest_framework import status
    from rest_framework.response import Response
    from rest_test.serializers import AuthorModelSerialize
    from rest_test.models import Author
    class AuthorListView(APIView):
        def get(self, request):
            获取全部数据
            :param request: 
            :return: 
            authors = Author.objects.all()
    		# instance指定要序列化的对象
    		# 多个数据用many=True
            serialize = AuthorModelSerialize(instance=authors, many=True)
            return Response(serialize.data, status=status.HTTP_200_OK)
        def post(self, request):
            :param request: 
            :return: 
            serialize = AuthorModelSerialize(data=request.data)
            # raise_exception=True,假如报错,会自动处理
            # 返回状态码:400,且数据的形式:{"phone": ["Ensure this field has no more than 11 characters."]}
            serialize.is_valid(raise_exception=True)
            serialize.save()
            return Response(serialize.data, status=status.HTTP_201_CREATED)
    

    别忘了写路由规则:

    from django.contrib import admin
    from django.urls import path
    from rest_test import views
    urlpatterns = [
        path('admin/', admin.site.urls),
    	# 同样调用.as_view方法
        path("authors/", views.AuthorListView.as_view())
    

    在原来的基础上多了主键,用来操作单个数据:

  • 同样继承APIView
  • 定义get put delete四个方法
  • 使用get_object_or_404取得要操作的对象(不用我们处理异常)
  • from rest_framework.views import APIView
    from rest_framework import status
    from rest_framework.response import Response
    from rest_test.serializers import AuthorModelSerialize
    from rest_test.models import Author
    from django.shortcuts import get_object_or_404
    class AuthorDetailView(APIView):
        def get(self, request, pk):
            # 这是django的内置方法,不会的可以查一下
            author = get_object_or_404(Author, pk=pk)
            serialize = AuthorModelSerialize(instance=author)
            return Response(serialize.data, status=status.HTTP_200_OK)
        def put(self, request, pk):
            author = get_object_or_404(Author, pk=pk)
            # 兼容patch方法的部分更新,一般我们都不会在定义个patch方法
            serialize = AuthorModelSerialize(instance=author, data=request.data, partial=True)
            serialize.is_valid(raise_exception=True)
            serialize.save()
            return Response(serialize.data, status.HTTP_201_CREATED)
        def delete(self, request, pk):
            author = get_object_or_404(Author, pk=pk)
            author.delete()
            return Response(status=status.HTTP_204_NO_CONTENT)
    
    from django.contrib import admin
    from django.urls import path
    from rest_test import views
    urlpatterns = [
        path('admin/', admin.site.urls),
        path("authors/", views.AuthorListView.as_view()),
        path("authors/<int:pk>/", views.AuthorDetailView.as_view())
    

    二级视图GenericAPIView

    一级视图已经可以很好地帮我们完成任务了,但是上面地代码明显有冗余部分:
    如,详情视图调用3次get_object_or_404,所以Django REST framework有更好地方法定义。

    GenericAPIViewAPIView的子类,它将我们用到的序列化器和模型已经获取model对象的方法进行了集成,即添加了常用的行为和属性。虽然我们也可以做到这样,但是它已经做好了,那么我们只需要了解规则就可以直接使用了。
    GenericAPIView有4个重要属性和3个重要方法:

    1. queryset: 通用的数据集,通常是XXX.objects.all()
    2. serializer_class: 通用的序列化器
    3. lookup_field: 主键的名称,默认是pk,主键,请注意与lookup_url_kwarg区分
    4. lookup_url_kwargurl参数中的标识,如:/books/1 若:lookup_url_kwargbooks_id,那么1这个值就要用books_id接收
  • 行为(方法)
    1. get_queryset: 获取queryset的数据集
    2. get_serializer: 获取serializer_class序列化器对象
    3. get_object: 根据lookup_field获取单个对象
  • 需要用到queryset()serializer_class()get_queryset()get_serializer()

    from rest_framework.generics import GenericAPIView
    from rest_framework import status
    from rest_framework.response import Response
    from rest_test.serializers import AuthorModelSerialize
    from rest_test.models import Author
    class AuthorListView(GenericAPIView):
       queryset = Author.objects.all()
       serializer_class = AuthorModelSerialize
       def get(self, request):
           serialize = self.get_serializer(instance=self.get_queryset(), many=True)
           return Response(serialize.data, status=status.HTTP_200_OK)
       def post(self, request):
           data = request.data
           serialize = self.get_serializer(data=data)
           serialize.is_valid(raise_exception=True)
           serialize.save()
           return Response(serialize.data, status=status.HTTP_201_CREATED)
    

    路由如一级视图, 保持不变

    4个属性和3个方法,都可以用,随便发挥。

    from rest_framework.generics import GenericAPIView
    from rest_framework import status
    from rest_framework.response import Response
    from rest_test.serializers import AuthorModelSerialize
    from rest_test.models import Author
    class AuthorDetailView(GenericAPIView):
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerialize
        lookup_url_kwarg = "author_id"
        def get(self, request, author_id):
            # 相当于get_object_or_404
            instance = self.get_object()
            serialize = self.get_serializer(instance=instance)
            return Response(serialize.data, status=status.HTTP_200_OK)
        def put(self, request, author_id):
            data = request.data
            instance = self.get_object()
            serialize = self.get_serializer(instance=instance, data=data, partial=True)
            serialize.is_valid(raise_exception=True)
            serialize.save()
            return Response(serialize.data, status.HTTP_201_CREATED)
        def delete(self, request, author_id):
            instance = self.get_object()
            instance.delete()
            return Response(status=status.HTTP_204_NO_CONTENT)
    

    由于指定了lookup_url_kwarg,路由改变

    from django.contrib import admin
    from django.urls import path
    from rest_test import views
    urlpatterns = [
        path('admin/', admin.site.urls),
        path("authors/", views.AuthorListView.as_view()),
        path("authors/<int:author_id>/", views.AuthorDetailView.as_view())
    

    与Mixin类结合

    二级视图的另一特点就是可以与Minx类相结合。

    python的Mixin类通常只是为子类类提供某些功能,而不自己实例化的类

    Django REST framework有这几个Mixin类可以为我们提供通用的列表视图和详情视图解决方法:

  • 视图列表: ListModelMixin + CreateModelMixin
  • 详情视图: RetrieveModelMixin + UpdateModelMixin + DestroyModelMixin
  • 除此之外,你还可以根据需求任意组合

    使用起来也特别简单

  • 继承GenericAPIView对应的Mixin
  • 定义方法(get, post, put, delete
  • 返回Mixin类中的处理方法(记得传入request等参数)
  • 列表视图
    from rest_framework.generics import GenericAPIView
    from rest_framework.mixins import ListModelMixin, CreateModelMixin
    from rest_test.serializers import AuthorModelSerialize
    from rest_test.models import Author
    class AuthorListView(GenericAPIView, ListModelMixin, CreateModelMixin):
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerialize
        def get(self, request):
            return self.list(request=request)
        def post(self, request):
            return self.create(request=request)
    from rest_framework.generics import GenericAPIView
    from rest_framework.mixins import UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin
    from rest_test.serializers import AuthorModelSerialize
    from rest_test.models import Author
    class AuthorDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerialize
        lookup_url_kwarg = "author_id"
        def get(self, request, author_id):
            return self.retrieve(request, author_id)
        def put(self, request, author_id):
            return self.update(request, author_id)
        def delete(self, request, author_id):
            return self.destroy(request, author_id)
    

    虽然Mixin类已经写好了处理逻辑,但是它是通过调用其它方法的形式实现的,所以我们可以重写部分方法,可以使Mixin类更加灵活。

    如:DestroyModelMixin类,这是源码:

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

    重写perform_destroy方法就可以实现自订制了,具体有哪些方法可以查看源码,这里不展开了

    三级视图xxxAPIView

    Django REST framework的视图简直跟套娃一样,一层套一层

    所谓的三级视图,就是将GenericAPIView和一个Mixin类继承到一个子类中,实现列表视图和详情视图的某个方法

    有一说一,如果没有大量自定义的行为, 确实可以使用三级视图快速构建一个项目

    常见的三级视图:

    from rest_framework.generics import CreateAPIView, ListAPIView, RetrieveAPIView, UpdateAPIView, DestroyAPIView
    from rest_test.serializers import AuthorModelSerialize
    from rest_test.models import Author
    class AuthorListView(ListAPIView, CreateAPIView):
        # 列表视图
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerialize
    class AuthorDetailView(RetrieveAPIView, UpdateAPIView, DestroyAPIView):
        # 详情视图
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerialize
        lookup_url_kwarg = "author_id"
    

    视图集就是将整个列表视图和详情视图变成一个类,然后我们只需要指定queryset serializer_class lookup_field lookup_url_kwarg即可。

  • 可以将一组相关的操作,放在一个类中进行完成
  • 处理方法变化了:
  • get全部 --> list
  • post --> create
  • get单个 --> retrieve
  • put --> update
  • delete --> destroy
  • 视图,用一个类即可定义全部方法:

    from rest_framework.viewsets import ViewSet
    from django.shortcuts import get_object_or_404
    from rest_framework import status
    from rest_framework.response import Response
    from rest_test.serializers import AuthorModelSerialize
    from rest_test.models import Author
    class AuthorViewSet(ViewSet):
       def list(self, request):
           authors = Author.objects.all()
           serialize = AuthorModelSerialize(instance=authors, many=True)
           return Response(serialize.data, status=status.HTTP_200_OK)
       def create(self, request):
           data = request.data
           serialize = AuthorModelSerialize(data=data)
           serialize.is_valid(raise_exception=True)
           serialize.save()
           return Response(serialize.data, status=status.HTTP_201_CREATED)
       def retrieve(self, request, author_id):
           author = get_object_or_404(Author, pk=author_id)
           serialize = AuthorModelSerialize(instance=author)
           return Response(serialize.data, status=status.HTTP_200_OK)
       def update(self, request, author_id):
           data = request.data
           author = get_object_or_404(Author, pk=author_id)
           serialize = AuthorModelSerialize(instance=author, data=data, partial=True)
           serialize.is_valid(raise_exception=True)
           serialize.save()
           return Response(serialize.data, status=status.HTTP_201_CREATED)
       def destroy(self, request, author_id):
           author = get_object_or_404(Author, pk=author_id)
           author.delete()
           return Response(status=status.HTTP_204_NO_CONTENT)
    

    做路由映射,由于列表视图和详情视图的url不一样,所以需要做两次映射:

    from django.contrib import admin
    from django.urls import path
    from rest_test import views
    urlpatterns = [
       path('admin/', admin.site.urls),
       path("authors/", views.AuthorViewSet.as_view({"get": "list", "post": "create"})),
       path("authors/<int:author_id>/",
            views.AuthorViewSet.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}))
    

    路由映射的意思就是,这个url的哪个请求方式,去哪个方法处理
    路由中的as_view方法: 参数是一个字典, 键为请求方式, 值为对应的处理函数
    如: path("authors/", views.AuthorViewSet.as_view({"get": "list", "post": "create"}))
    意为:authors/get方法去list函数那里处理,而post方法去create函数那里处理

    GenericViewSet

    下面3个类都继承了GenericAPIView,操作起来更简单

    from rest_framework import status
    from rest_framework.response import Response
    from rest_test.serializers import AuthorModelSerialize
    from rest_test.models import Author
    from rest_framework.viewsets import GenericViewSet
    class AuthorViewSet(GenericViewSet):
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerialize
        lookup_url_kwarg = "author_id"
        def list(self, request):
            authors = self.get_queryset()
            serialize = self.get_serializer(instance=authors, many=True)
            return Response(serialize.data, status=status.HTTP_200_OK)
        def create(self, request):
            data = request.data
            serialize = self.get_serializer(data=data)
            serialize.is_valid(raise_exception=True)
            serialize.save()
            return Response(serialize.data, status=status.HTTP_201_CREATED)
        def retrieve(self, request, author_id):
            author = self.get_object()
            serialize = self.get_serializer(instance=author)
            return Response(serialize.data, status=status.HTTP_200_OK)
        def update(self, request, author_id):
            data = request.data
            author = self.get_object()
            serialize = self.get_serializer(instance=author, data=data, partial=True)
            serialize.is_valid(raise_exception=True)
            serialize.save()
            return Response(serialize.data, status=status.HTTP_201_CREATED)
        def destroy(self, request, author_id):
            author = self.get_object()
            author.delete()
            return Response(status=status.HTTP_204_NO_CONTENT)
    

    路由使用ViewSet的代码

    ReadOnlyModelViewSet
    只读的视图集

    from rest_framework.viewsets import ReadOnlyModelViewSet
    from rest_test.serializers import AuthorModelSerialize
    from rest_test.models import Author
    class AuthorReadOnlyViewSet(ReadOnlyModelViewSet):
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerialize
        lookup_url_kwarg = "author_id"
    
    from django.contrib import admin
    from django.urls import path
    from rest_test import views
    urlpatterns = [
        path('admin/', admin.site.urls),
        path("authors/", views.AuthorReadOnlyViewSet.as_view({"get": "list"})),
        path("authors/<int:author_id>/", views.AuthorReadOnlyViewSet.as_view({"get": "retrieve"}))
    

    ModelViewSet
    模型字符集,把列表视图和详情视图已经集成在一起了
    注意:部分更新的函数是partial_update

    from rest_framework.viewsets import ModelViewSet
    from rest_test.serializers import AuthorModelSerialize
    from rest_test.models import Author
    class AuthorViewSet(ModelViewSet):
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerialize
        lookup_url_kwarg = "author_id"
    
    from django.contrib import admin
    from django.urls import path
    from rest_test import views
    urlpatterns = [
        path('admin/', admin.site.urls),
        path("authors/", views.AuthorViewSet.as_view({"get": "list", "post": "create"})),
        path("authors/<int:author_id>/",
             views.AuthorViewSet.as_view(
                 {"get": "retrieve", "put": "update", "delete": "destroy", "patch": "partial_update"}))
    
  • 定义视图集
    from rest_framework.viewsets import ReadOnlyModelViewSet
    from rest_test.serializers import AuthorModelSerialize
    from rest_test.models import Author
    # 为了看起来更简洁,使用ReadOnlyModelViewSet
    class AuthorReadOnlyViewSet(ReadOnlyModelViewSet):
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerialize
        lookup_url_kwarg = "author_id"
        def gt_id(self, request):
            queryset = self.filter_queryset(self.get_queryset())
            authors = queryset.filter(pk__gt=4)
            serialize = self.get_serializer(instance=authors, many=True)
            return Response(serialize.data, status=status.HTTP_200_OK)
    
  • 写路由映射关系
    from django.contrib import admin
    from django.urls import path
    from rest_test import views
    urlpatterns = [
        path('admin/', admin.site.urls),
        path("authors/", views.AuthorReadOnlyViewSet.as_view({"get": "list"})),
        path("authors/<int:author_id>/", views.AuthorReadOnlyViewSet.as_view({"get": "retrieve"})),
        # 注意这里 !!!!!!!! get请求方式,找gt_id方法
        path("authors/gt4/", views.AuthorReadOnlyViewSet.as_view({"get": "gt_id"}))
    

    注意:假如使用Django REST framework的方式定义路由的话,不能像上面那样写,要这样

    from rest_framework.viewsets import ReadOnlyModelViewSet
    from rest_test.serializers import AuthorModelSerialize
    from rest_test.models import Author
    from rest_framework.decorators import action
    class AuthorReadOnlyViewSet(ReadOnlyModelViewSet):
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerialize
        lookup_url_kwarg = "author_id"
        # 生成路由规则: 前缀/方法名/  detail=True时,需要主键的值且url为:前缀/{pk}/方法名/
        @action(methods=['GET'], detail=False)  
        def gt_id(self, request):
            queryset = self.filter_queryset(self.get_queryset())
            authors = queryset.filter(pk__gt=7)
            serialize = self.get_serializer(instance=authors, many=True)
            return Response(serialize.data, status=status.HTTP_200_OK)
    

    至于路由如何写,见下文的路由部分

    捋一下思路

  • 一级视图只是继承了View,用法和原生Django十分相似,几乎所有方法都要我们写。
  • 二级视图将序列化器和模型数据定义在了类属性中,并且提供了获取单个模型的方法(get_object),简化了部分操作。最重要的是它可以与Mixin类结合,能够减少的部分工作,但是仍然需要自定义方法,然后再方法中调用Mixin类的方法。
  • 三级视图则是父类直接提供方法,我们继承该类就可以使用了。
  • 这三种视图定义方法,有一个明显的缺点 (可能吧) : 我们必须要定义多个视图类(列表视图和详细视图,要添加额外方法的话,可能还要增加视图数量)
  • 视图集可以在路由中进行路由映射,因此可以把所有处理方法都定义在同一个类里面,而且它也提供了从简单到复杂的视图集类,方便我们的使用
  • 至于在实际开发过程中,要用哪个类,全凭自己选择吧,最多多写几段代码而已
  • SimpleRouter

    一般使用:

  • router = SimpleRouter()
  • 调用register方法
  • urlpatterns += router.urls
  • from django.contrib import admin
    from django.urls import path
    from rest_test import views
    from rest_framework.routers import DefaultRouter, SimpleRouter
    urlpatterns = [
        path('admin/', admin.site.urls),
    router = SimpleRouter()
    router.register("authors", views.AuthorReadOnlyViewSet, basename="authors")
    # 是authors不是authors/
    urlpatterns += router.urls
    print(urlpatterns)
    [<URLResolver <URLPattern list> (admin:admin) 'admin/'>, 
    <URLPattern '^authors/$' [name='authors-list']>, 
    <URLPattern '^authors/(?P<author_id>[^/.]+)/$' [name='authors-detail']>,
    <URLPattern '^authors/$' [name='authors-list']>, 
    <URLPattern '^authors/(?P<author_id>[^/.]+)/$' [name='authors-detail']>]
    

    DefaultRouter

    该路由可以在SimpleRouterd的基础上生成可选的.json样式格式后缀的路由和根路由。

    from django.contrib import admin
    from django.urls import path
    from rest_test import views
    from rest_framework.routers import DefaultRouter, SimpleRouter
    urlpatterns = [
        path('admin/', admin.site.urls),
    router = DefaultRouter()
    router.register("authors", views.AuthorReadOnlyViewSet, basename="authors")
    # 是authors不是authors/
    urlpatterns += router.urls
    print(urlpatterns)
    [<URLResolver <URLPattern list> (admin:admin) 'admin/'>,
    <URLPattern '^authors/$' [name='authors-list']>, 
    <URLPattern '^authors\.(?P<format>[a-z0-9]+)/?$' [name='authors-list']>,
    <URLPattern '^authors/gt_id/$' [name='authors-gt-id']>, 
    <URLPattern '^authors/gt_id\.(?P<format>[a-z0-9]+)/?$' [name='authors-gt-id']>, 
    <URLPattern '^authors/(?P<author_id>[^/.]+)/$' [name='authors-detail']>, 
    <URLPattern '^authors/(?P<author_id>[^/.]+)\.(?P<format>[a-z0-9]+)/?$' [name='authors-detail']>, 
    <URLPattern '^authors/$' [name='authors-list']>,
    <URLPattern '^authors\.(?P<format>[a-z0-9]+)/?$' [name='authors-list']>, 
    <URLPattern '^authors/(?P<author_id>[^/.]+)/$' [name='authors-detail']>, 
    <URLPattern '^authors/(?P<author_id>[^/.]+)\.(?P<format>[a-z0-9]+)/?$' [name='authors-detail']>, 
    <URLPattern '^$' [name='api-root']>, <URLPattern '^\.(?P<format>[a-z0-9]+)/?$' [name='api-root']>]
    

    这部分为限制访问的内容,由认证、权限和限流三个部分组成。

    Authentication

    此部分讲的是认证内容,即如何确定你是你??

    有两种配置方式:

  • settings中配置全局的
    如:
    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework.authentication.BasicAuthentication',
            'rest_framework.authentication.SessionAuthentication',
    
  • 在视图中配置本视图的
    from rest_framework.authentication import SessionAuthentication, BasicAuthentication
    class BookInfoModelViewSet(ModelViewSet):
        authentication_classes = (SessionAuthentication, BasicAuthentication)
    

    一般来说我们常用有这几种验证方式:

  • BasicAuthentication
    基于HTTP的认证方式,要求验证的时候会弹出一个框来,让你输入账号和密码,一般只在开发测试时使用
  • SessionAuthentication
    基于Session的认证,这里应该用的时django自带的AUTH组件
  • TokenAuthentication
    基于Token的HTTP认证方案,即生成一个令牌,作为标识,请求头带上Authorization: Token token的值即可
  • JSON Web Token Authentication
    基于jwt的认证方式
  • 注:可以多个认证方式同时使用

    下面将说如何使用

    BasicAuthentication

    全局配置:

    REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ( 'rest_framework.authentication.BasicAuthentication', 'DEFAULT_PERMISSION_CLASSES': ( # 'rest_framework.permissions.IsAuthenticated', # 普通用户 # 'rest_framework.permissions.AllowAny', # 所有用户 'rest_framework.permissions.IsAdminUser', # 管理员户

    DEFAULT_PERMISSION_CLASSES是权限,后面会讲到,可以先看看注释

    假如我们要求有一定权限才能登录的话,那么它就会弹出如下面这样的对话框:
    这里登录用的是django的用户账号(就是登录django admin的那种账号),是可以扩展的

    SessionAuthentication

    使用SessionAuthentication首先就是需要注意CSRF的问题

    全局配置(只换来认证器):

    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": (
            'rest_framework.authentication.SessionAuthentication',
        'DEFAULT_PERMISSION_CLASSES': (
            # 'rest_framework.permissions.IsAuthenticated', #普通用户
            # 'rest_framework.permissions.AllowAny', #所有用户
            'rest_framework.permissions.IsAdminUser',  # 管理员户
    

    登录页面需要我们自己写,很简单,可以看我这篇文章Django auth。但这里我们是学习DRF,那么就用一个REST API接口登录:
    如果我们将登录看作是创建一个在线用户,那么logout就是删除一个在线用户了。

    # views.py
    from rest_framework.views import APIView
    from rest_framework.permissions import AllowAny
    from django.contrib.auth import authenticate, login, logout
    class SessionView(APIView):
        permission_classes = (AllowAny, )
        # 必须写AllowAny(即每个人都可以访问),不然连登录的权限都没有!!
        def post(self, request):
            username = request.data.get("username")
            password = request.data.get("password")
            user = authenticate(request, username=username, password=password)
            if user is not None:
                # 登录,写入session信息
                login(request, user)
                return Response({"detail": "success!"}, status=status.HTTP_201_CREATED)
            else:
                return Response({"detail": "password or username error!"}, status=status.HTTP_400_BAD_REQUEST)
        def delete(self, request):
            logout(request)
            return Response({"detail": "logout success!!"}, status=status.HTTP_204_NO_CONTENT)
    

    然后再添加路由

    urlpatterns = [
        path('admin/', admin.site.urls),
        path("session/", views.SessionView.as_view())
    

    访问session/页面,方式POST请求登录即可。

    TokenAuthentication

  • 配置settings:
    INSTALLED_APPS = [
     "rest_framework.authtoken"
    # ...
    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": (
            'rest_framework.authentication.TokenAuthentication',
        'DEFAULT_PERMISSION_CLASSES': (
            # 'rest_framework.permissions.IsAuthenticated', #普通用户
            # 'rest_framework.permissions.AllowAny', #所有用户
            'rest_framework.permissions.IsAdminUser',  # 管理员用户
    
  • 执行迁移命令,这时会生成一个authtoken_token
    $python manage.py migrate
    
  • 编写登录视图
    from rest_framework.views import APIView
    from rest_framework.permission import AllowAny
    from django.contrib.auth import authenticate
    from rest_framework.authtoken.models import Token
    class TokenView(APIView):
        permission_classes = (AllowAny,)
        def post(self, request):
            username = request.data.get("username")
            password = request.data.get("password")
            user = authenticate(request, username=username, password=password)
            if user is not None:
                # 删除原有的Token
                old_token = Token.objects.filter(user=user)
                old_token.delete()
                # 创建新的Token
                token = Token.objects.create(user=user)
                return Response({"code": 0,
                                 "msg": "login success!",
                                 "username": user.username,
                                 "token": token.key}, status=status.HTTP_201_CREATED)
            else:
                return Response({"detail": "password or username error!"}, status=status.HTTP_400_BAD_REQUEST)
        def delete(self, request):
            if request.user.is_authenticated:
                user = request.user
                old_token = Token.objects.filter(user=user)
                old_token.delete()
            return Response({"detail": "logout success!!"}, status=status.HTTP_204_NO_CONTENT)
    
  • 配置路由
    from django.contrib import admin
    from django.urls import path
    from rest_test import views
    urlpatterns = [
        path('admin/', admin.site.urls),
        path("token/", views.TokenView.as_view())
    
  • 将用户名和密码利用POST方式提交给token后就会返回token
    获得token后,将token放在请求头即可。
    如:获取token为:323ce6f331c7aa65836ad48169037f001ce7e18f
    那么请求头就为:Authorization: Token 323ce6f331c7aa65836ad48169037f001ce7e18f
  • JSON Web Token Authentication

    安装djangorestframework-simplejwt,这是目前官网推荐的python jwt库

    $pip install djangorestframework-simplejwt
    

    配置验证器为:

    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": (
            'rest_framework_simplejwt.authentication.JWTAuthentication',
        'DEFAULT_PERMISSION_CLASSES': (
            'rest_framework.permissions.IsAuthenticated',  # 普通用户
            # 'rest_framework.permissions.AllowAny',       # 所有用户
            # 'rest_framework.permissions.IsAdminUser',    # 管理员用户
    	'ACCESS_TOKEN_LIFETIME': datetime.timedelta(minutes=30),
    	# token刷新后的有效时间
    	'REFRESH_TOKEN_LIFETIME': datetime.timedelta(days=1)
    

    定义路由规则
    由于simplejwt已经写好了视图,我们只需要直接拿来用即可:

    from django.contrib import admin
    from django.urls import path
    from rest_framework_simplejwt.views import (
    	TokenObtainPairView,
    	TokenRefreshView,
    	TokenVerifyView
    urlpatterns = [
    	path('admin/', admin.site.urls),
    	# 获取Token的接口
    	path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    	# 刷新Token有效期的接口
    	path('api/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
    	# 验证Token的有效性
    	path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
    

    将用户名和密码提交到POST api/token/
    返回的信息包括refreshaccess两个字段。
    其中refresh是用于刷新token的(每个token都是有时间限制的,过了时间就失效了)
    access是用于后续的请求时验证身份的。

    access的值放入请求头中:
    如:获取的accesseyJ0eiJIUiJ9.eyJ0b2tlzZXJjF9.n5FTesf9i4X3t7X0JEQ
    那么,请请求头就是:Authorization: Bearer eyJ0eiJIUiJ9.eyJ0b2tlzZXJjF9.n5FTesf9i4X3t7X0JEQ

    上面的路由:
    api/token/verify/ 可以检验token,需要POST access的值
    api/refresh/ 可以刷新token,需要POST refresh的值

    关于更多simple-jwt的信息,请查看官方文档:Simple JWT

    permission

    权限可以是能不能让用户访问到页面的关键,不过你得先通过了认证。

    通用可以时全局和视图的

  • 全局(在settings中定义):
    REST_FRAMEWORK = {
        'DEFAULT_PERMISSION_CLASSES': (
            'rest_framework.permissions.IsAuthenticated',  # 普通用户
    
  • 视图(在视图中定义):
    from rest_framework.permissions import AllowAny
    from rest_framework.viewsets import ModelViewSet
    class BookInfoModelViewSet(ModelViewSet):
        permission_classes = (AllowAny,)
        # ...
    

    rest_framework.permissions.IsAuthenticatedOrReadOnly
    允许经过身份验证的用户执行任何请求,匿名用户可以仅仅可以读取(GET

    rest_framework.permissions.IsAuthenticated
    仅允许经过身份验证的用户执行任何请求(即已经登录的用户才能访问)

    rest_framework.permissions.IsAdminUser
    仅允许request.user.is_staffTrue的用户执行任何请求(说明这是一个管理员账号)

    rest_framework.permissions.DjangoModelPermissions
    仅允许用户通过身份验证并分配了相关模型权限的情况下的用户执行任何请求

    特别注意:此权限只能应用于具有queryset属性集的视图

    DjangoModelPermissions

    由于内置的权限(只有增删改查)粒度不够细,不足以应付复杂的需求,所有这里演示一些使用我们自定义的权限
    关于django的权限问题可以查看此文:权限
    要使用自定义模型权限,请继承DjangoModelPermissions并设置perms_map属性

    DjangoModelPermissions的部分源码:

    perms_map = {
        'GET': [],
        'OPTIONS': [],
        'HEAD': [],
        'POST': ['%(app_label)s.add_%(model_name)s'],
        'PUT': ['%(app_label)s.change_%(model_name)s'],
        'PATCH': ['%(app_label)s.change_%(model_name)s'],
        'DELETE': ['%(app_label)s.delete_%(model_name)s'],
    
    # views.py
    class CustomPermClass(DjangoModelPermissions):
        # GET请求权限, can_publish
        perms_map = {"GET": ['%(app_label)s.can_publish']}
    class AuthorViewSet(ModelViewSet):
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerialize
        lookup_url_kwarg = "author_id"
        permission_classes = (CustomPermClass,)
    # models.py
    class Books(models.Model):
        bid = models.AutoField(primary_key=True)
        name = models.CharField(max_length=32, verbose_name="书名")
        description = models.CharField(max_length=128, verbose_name="书籍描述")
        author = models.ForeignKey(Author, on_delete=models.CASCADE)
        def __str__(self):
            return f"{self.author.name}: 《{self.name}》"
        class Meta:
            permissions = (("can_publish", "能够出版"),)
    

    另: DRF有与Django的标准对象权限框架相关联的类, 见: DjangoObjectPermissions

    即让请求在一段时间内只能访问多少次。

  • 全局定义
    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_CLASSES': [
            'rest_framework.throttling.AnonRateThrottle', # 匿名用户
            'rest_framework.throttling.UserRateThrottle'  # 认证用户
        'DEFAULT_THROTTLE_RATES': {
            'anon': '2/minute',     # 匿名用户每分钟2次
            'user': '3/minute'        # 认证用户每分钟3次
    
  • 视图中定义
    from rest_framework.throttlingimport AnonRateThrottle
    from rest_framework.viewsets import ModelViewSet
    class BookInfoModelViewSet(ModelViewSet):
        throttle_classes = (AnonRateThrottle, )
        # ...
    
  • 可选限流
    # settings.py
    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_CLASSES': [
            'rest_framework.throttling.ScopedRateThrottle',
        'DEFAULT_THROTTLE_RATES': {
            'downloads': '3/minute',    # downloads 自定义的
            'uploads': '5/minute'        # uploads 自定义的
    
    # views.py
    class TestView(APIView):
        throttle_scope = "uploads"
        def get(self,request):
            return Response("testing....")
    

    筛选返回数据

    使用默认的分页器

    REST_FRAMEWORK = {
        'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
        'PAGE_SIZE': 2,
    
  • PAGE_SIZE指的是每页多少条数据
  • 其默认生成的数据是这样的:

    "count": 9, "next": "http://127.0.0.1:8000/authors/?limit=2&offset=6", "previous": "http://127.0.0.1:8000/authors/?limit=2&offset=2", "results": [ "aid": 8, "name": "张三", "phone": "13888888888", "address": "广东省广州市xxxxx", "books_set": [] "aid": 9, "name": "李四", "phone": "13888888888", "address": "广东省广州市xxxxx", "books_set": []

    http://127.0.0.1:8000/authors/?limit=2&offset=6中的limit指取多少条数据,offset指偏移量,即跳过多少条数据
    默认情况下limit是没有限制的,也就是说你指定1000000都行,为了限度这些行为,我们可以自定义一个分页器

    自定义一个分页器

    只需要继承rest_framework.pagination.PageNumberPagination,里面我们可以通过自定义类属性的形式,自定义分页器:
    常用的属性

  • page_size 页面大小的数值
  • page_size_query_param 可以指定页面大小
  • max_page_size 指示允许的最大请求页面大小
  • 还有些没有列出来,可以查看官方文档

    #自定义分页对象 class MyPageNumberPagination(PageNumberPagination): #1,默认的大小 page_size = 3 #2,前端可以指定页面大小 page_size_query_param = 'page_size' #3,页面的最大大小 max_page_size = 5 # 视图集 class BookInfoModelViewSet(ModelViewSet): # ... pagination_class = MyPageNumberPagination # 链接可以是,?page=4 或者 ?page=4&page_size=100

    这个功能可以让数据按照指定的字段进行排序

    from rest_framework.filters import OrderingFilter
    class BookInfoModelViewSet(ModelViewSet):
        # ....
        # 局部排序
        filter_backends = (OrderingFilter, ) 
        # 指定排序字段
        ordering_fields = ['id', 'btitle','bread']
        # 查询格式: ?ordering=-bread,id
    

    过滤功能可以根据文档配置,进行过滤返回的数据

    这不是Django REST framework的组件,需要我们安装django-filter

  • 安装django-filter
    $pip install django-filter
    
  • django-filter配置到INSTALLED_APPS
    INSTALLED_APPS = [
        # ...
        'django_filters',
    
  • 配置filter
    全局:
    REST_FRAMEWORK = {
        # ...
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
    视图:
    from django_filters.rest_framework import DjangoFilterBackend
    class BookInfoModelViewSet(ModelViewSet):
        # ....
      filter_backends = (DjangoFilterBackend, )
      filterset_fields = ('id', 'btitle',"is_delete")
      # 参数格式:?id=1&is_delete=True
    

    除此外还可以向ORM的__方法一样指定参数,但是要实现这个功能需要自定义FilterSet

    有更加复杂定义方法 (FilterSet), 详情见: 官方文档

    我们可以通过配置,指定某些函数处理程序中的异常信息

    自定义一个异常处理函数:

    # myapp.my_exception.py
    from rest_framework.views import exception_handler
    from rest_framework.response import Response
    from django.db import DatabaseError
    def custom_exception_handler(exc, context):
        #1 调用系统方法,处理了APIException的异常,或者其子类异常
        response = exception_handler(exc, context)
        #2 判断response是否有值
        if response is not None:
            response.data['status_code'] = response.status_code
        else:
            if isinstance(exc, DatabaseError):
                response = Response("数据库大出血")
            else:
                response = Response("其他异常!")
        return response
    

    将处理函数配置到REST_FRAMEWORK配置中

    REST_FRAMEWORK = {
        'EXCEPTION_HANDLER': 'myapp.my_exception.custom_exception_handler'
    

    生成API文档

    该功能可以生成一个文档
    操作流程:

  • 安装扩展
    $pip install coreapi
    
  • 路由规则,在跟路由下:
    from rest_framework.documentation import include_docs_urls
    urlpatterns = [
    # ...
    path('^docs/', include_docs_urls(title='我的API文档'))    # title是标题,
    
  • 添加备注信息
    配置视图集的__doc__和序列化器的help_textlabel
    
    class CourseViewSet(viewsets.ModelViewSet):
        retrieve:
            返回指定course信息
        list:
            返回course列表
        update:
            更新course信息
        destroy:
            删除course记录
        create:
            创建course记录
        partial_update:
            更新部分字段
    #在view中的资源类下,说明注释信息
    class Course(models.Model):
        name = models.CharField(max_length=64,verbose_name='课程名称',help_text='课程名称')
    #在model或者serializer中添加help_text字段。它会显示在api文档的字段描述信息中
    
  • 打开http://127.0.0.1:8008/docs/即可查看文档
  •