DRF 参考文档

2020 年 06 月 04 日 • 阅读数: 202

Django REST Framework

Django REST Framework

简介

Django REST framework 是一个基于Django的用于构建 Web API 的强大而又灵活的框架

特点包括:

  • 构建支持视图的 Web browsable API
  • 支持 OAuth1aOAuth2 的身份验证策略
  • 支持序列化 ORMnon-ORM 数据源
  • 支持 FBVCBV 的编码方式
  • 丰富的文档支持,和强大的社区支持

安装

依赖的第三方库

  • coreapi (1.32.0+) - Schema generation support.
  • Markdown (2.1.0+) - Markdown support for the browsable API.
  • django-filter (1.0.1+) - Filtering support.
  • django-crispy-forms (1.7.0+) - Improved HTML display for filtering.
  • django-guardian (1.1.1+) - Object level permissions support.

通过pip安装

pip install djangorestframework
pip install markdown       # Markdown support for the browsable API.
pip install django-filter  # Filtering support

注意:其他的依赖库使用同样的方式安装

DRF框架基于Django,所以默认需要安Django(Django的操作本文档不会做详细解释,直接使用)

添加 'rest_framework' 到Django的 INSTALLED_APPS 设置中

INSTALLED_APPS = (
    ...
    'rest_framework',
)

通过浏览器访问API,还需要添加REST框架的登录和注销视图路由

urlpatterns = [
    ...
    path('api-auth/',include('rest_framework.urls', namespace='rest_framework')),
]

第一章:序列化层

Serializer

Serializer允许我们把像查询集和模型实例这样的复杂数据转换为可以轻松渲染成 JSONXML 或其他内容类型的原生Python类型

Serializer还提供反序列化和验证的功能,在验证传入的数据之后允许解析数据转换回复杂类型

Serializer的使用和Django中的From表单的使用类似,定义了字段之后,在调用的时候,只需要将数据传入进来,它就可以会指定的字段进行序列化

from rest_framework import serializers
from goods.models import Goods

class GoodsSerializer(serializers.Serializer):
    """
    商品的序列化类
    """
    name = serializers.CharField(required=True, max_length=100)
    click_num = serializers.IntegerField(default=0)

如果仅仅是配置了字段的话,还不能够做到反序列化,我们还需要重写它的 create() 方法或者 update() 方法

当我们重写了 create() 方法的时候,它会将通过了验证的数据放到 create()validated_data 参数中

所有如果需要保存数据库,就可以直接调用model的 create() 方法,将验证后的数据传入即可

    def update(self, instance, validated_data):
        pass

    def create(self, validated_data):
        return Goods.object.create(**validated_data)

我们虽然重写了 create() 方法,但在视图层中我们,一般不会直接调用 create() 方法,而是调用 save() 方法,在内部它实际上做了一层判断,如果我们传递过去的值只有新的数据,那么它就会调用 create() 方法,苏果我们传递过去的值包含原始数据,它就会调用 update() 方法,具体例子如下:

# 我们只传递的新的数据,在save()的时候,它就会去调用serializer的create()方法
serializer = GoodsSerializer(data=request.data)

# 我们传递了原始数据,那么它在save()的时候,就会调用update()方法
serializer = GoodsSerializer(goods, data=request.data)

通过 save() 方法调用还有一个好处就是可以传递额外的参数,我们传递的参数会直接保存到 validated_data 中,不需要通过验证

if serializer.is_valid():
    serializer.save(shop_price=100)

**注:**一般我们调用 save() 方法都是在验证通过的情况下,否则会抛出异常

有些时候我们不希望保存数据库,而是在通过了验证之后,做进一步的操作,比如我们验证邮箱后,发送邮件

这时候我们也可以直接重写 save() 方法

ModelSerializer

前面所过Serializer的使用和Django中的From表单类似,所以它当然也提供了一种更加简洁的方式

它和不同的Serializer的区别主要有:

  • 它根据Model自动生成一组字段
  • 它自动生成序列化器的验证器
  • 它默认简单实现了 create() 方法和 update() 方法

使用方式如下:

  • 通过 model 指定映射的Model
  • 通过 fields 指定要映射的字段
  • 通过 exclude 指定不需要映射的字段
from goods.models import Goods

class GoodsSerializer(serializers.ModelSerializer):

    class Meta:
        model = Goods
        fields = ('name', 'click_num', 'market_price')

如果没有特定的字段要求可以给 fields 赋一个 '__all__' ,表示所有Model的字段都将映射到序列化器字段中

Model中任何关联字段比如外键都将映射到 PrimaryKeyRelatedField 字段

默认情况下不包括反向关联,除非使用显示包含(根据Model字段中的 related_name 属性)

Serializer的嵌套

首先需要知道的是 Serializer 本身也是一种 Filed ,所以我们可以将它赋值给一个字段,这样就达到了在一个序列化对象中嵌套另一个序列化对象的目的

class GoodsImageSerializer(serializers.ModelSerializer):

    class Meta:
        model = GoodsImage
        fields = ('image', )


class GoodsSerializer(serializers.ModelSerializer):
    images = GoodsImageSerializer(many=True)

    class Meta:
        model = Goods
        fields = '__all__'

**注:**如果嵌套的对象可能存在多个,一定要设置 many=True 这个参数

如果嵌套表示可以接收 None 值,则应该将 required=False 标志传递给嵌套的序列化器

我们也可以通过定义内部类的方式来实现嵌套序列化

class CategorySerializer(serializers.ModelSerializer):

    class SubCategorySerializer(serializers.ModelSerializer):

        class SubCategorySerializer(serializers.ModelSerializer):

            class Meta:
                model = GoodsCategory
                fields = '__all__'

        class Meta:
            model = GoodsCategory
            fields = '__all__'
        sub_cat = SubCategorySerializer(many=True)

    class Meta:
        model = GoodsCategory
        fields = '__all__'
    sub_cat = SubCategorySerializer(many=True)

数据的验证

反序列化数据的时候,你始终需要先调用 is_valid() 方法,然后再尝试去访问经过验证的数据或保存对象实例如果发生任何验证错误,errors 属性将包含表示生成的错误消息的字典

is_valid() 方法使用可选的 raise_exception 标志,如果存在验证错误将会抛出一个serializers.ValidationError 异常。

这些异常由REST framework提供的默认异常处理程序自动处理,默认将返回 HTTP 400 Bad Request 响应

# 如果数据无效就返回400响应
serializer.is_valid(raise_exception=True)

一般的我们可以在定义字段的时候,指定一些简单的验证,如下:

  • required:表示是否唯一
  • max_length:表示最大长度
from rest_framework import serializers
from goods.models import Goods

class GoodsSerializer(serializers.Serializer):
    """
    商品的序列化类
    """
    name = serializers.CharField(required=True, max_length=100)
    click_num = serializers.IntegerField(default=0)

当这些简单的验证无法满足我们的时候,也可以自定义字段级别的验证

你可以通过向你的 Serializer 子类中添加 validate_<field_name> 方法来指定自定义字段级别的验证,这些类似于Django表单中的 clean_<field_name> 方法,这些方法采用单个参数,即需要验证的字段值

你的 validate_<field_name> 方法返回一个验证过的数据或抛出 serializers.ValidationError 的异常

可以给抛出的异常传递一个参数,这回被写入到 errors

class SmsSerializer(serializers.Serializer):
    mobile = serializers.CharField(max_length=11)

    def validate_mobile(self, mobile):
        if User.objects.filter(mobile=mobile).count():
            raise serializers.ValidationError('用户已经存在')

        if not re.match(REGEX_MOBILE, mobile):
            raise serializers.ValidationError('手机验证非法')

        one_minutes_ago = datetime.datetime.now() - datetime.timedelta(
            hours=0, minutes=1, seconds=0)
        if VerifyCode.objects.filter(add_time__gt=one_minutes_ago, mobile=mobile):
            raise serializers.ValidationError('距离上次发送不足60秒')

        return mobile

注: 如果你在序列化器中声明 <field_name> 的时候带有 required=False 参数,字段不被包含的时候这个验证步骤就不会执行

当需要多个字段联合验证的时候,你也可以定义一个对象级别的验证

使用方法添加一个 validate() 方法到你的 Serializer 子类中,这个方法采用字段值字典的单个参数,如果需要应该抛出一个 ValidationError 异常,或者返回经过验证的值,例如:

class UserRegisterSerializer(serializers.ModelSerializer):

    def validate(self, attrs):
        attrs['mobile'] = attrs['username']
        del attrs['code']
        return attrs

    class Meta:
        model = User
        fields = ('username', 'code', 'password')

**注:**可以看出,在这个对象级别的验证里面你除了可以编写验证逻辑外,甚至可以添加或删除一个字段

验证器

序列化器上的各个字段都可以包含验证器,通过在字段实例上声明,例如:

def multiple_of_ten(value):
    if value % 10 != 0:
        raise serializers.ValidationError('Not a multiple of ten')

class GameRecord(serializers.Serializer):
    score = IntegerField(validators=[multiple_of_ten])
    ...

序列化器类还可以包括应用于一组字段数据的可重用的验证器。这些验证器要在内部的Meta类中声明,如下所示,UniqueTogetherValidator 表示联合唯一约束:

class UserFavSerializer(serializers.ModelSerializer):
    user = serializers.HiddenField(default=serializers.CurrentUserDefault())

    class Meta:
        model = UserFav
        validators = [
            UniqueTogetherValidator(
                queryset=UserFav.objects.all(),
                fields=('user', 'goods'),
                message='已经收藏')
        ]

        fields = ('user', 'goods', 'id')

第二章:视图层

APIView

APIView 是对Django的 View 的进一层封装

区别在于:

  • 传递给处理请求的函数的是REST框架的 Request 实例,而不是Django的 HttpRequest 实例
  • 处理请求的函数返回的是REST框架的 Response 实例,而不是Django的 HttpResponse 实例
  • 任何 APIException 都将被REST框架捕获,并进行适当的处理
  • 在请求传递给处理函数之前,对请求进行身份验证,和权限的检查
  • 可以通过配置为整个类添加一些策略属性,这些属性可以在全局中配置,也可以在单独的某个类中配置

使用 APIView 和使用普通的 View 类似,需要注意的是这里需要继承 rest_framework 中的 views.APIView

from rest_framework import views

class GoodListViewSet(views.APIView):
    
    def get(self, request):
        goods = Goods.objects.all()
        goods_serializer = GoodsSerializer(goods, many=True)
        return Response(goods_serializer.data)
    
        def post(self, request):
        serializer = GoodsSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

在API的编写中,最核心的事情就是,我们需要根据请求获取到对应的数据,然后返回 JSON 格式的数据,这其中可能会涉及到用户的认证,权限管理,数据过滤,搜索等功能

而这些功能我们几乎都可以在 APIView 的策略属性中找到,通过配置相应的策略属性,就可以完成以上功能

API策略属性:

  • renderer_classes
  • parser_classes
  • authentication_classes
  • throttle_classes
  • permission_classes
  • content_negotiation_class
  • metadata_class
  • versioning_class

Mixins和Generics

我们可以使用更加简洁的方式来或取商品的列表数据

rest_framework 提供了 mixinsgenerics 两个模块,通过这两个模块的组合使用可以简化我们大部分API的编写流程

在下面的代码中,我们继承了 mixins.ListModelMixin, generics.GenericAPIView 两个类

然后定义两个类属性 querysetserializer_class 然后重写 get 方法,返回调用 self.list 方法

from rest_framework import mixins, generics

from .serializers import GoodsSerializer
from .models import Goods


class GoodListView(mixins.ListModelMixin, generics.GenericAPIView):
    """
    商品列表页
    """
    queryset = Goods.objects.all()[:10]
    serializer_class = GoodsSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

通过以上代码,我们就完成了一个商品列表数据API的编写,但不仅如此,我们还可以使代码更加简洁

下面的例子是对上面的代码的进一步精简

from rest_framework import generics

from .serializers import GoodsSerializer
from .models import Goods


class GoodListView(generics.ListAPIView):
    """
    商品列表页
    """
    queryset = Goods.objects.all()[:10]
    serializer_class = GoodsSerializer

可以发现我们直接继承了 generics.ListAPIView 就不需要再重写 get 方法了,而且也不需要我们自己返回数据了, ListAPIView 为我们完成了一切,那么它是如何实现的呢

查看 ListAPIView 的源码可以发现,它的做法和我们一开始的代码如出一辙,重写 get 方法,调用 self.list

class ListAPIView(mixins.ListModelMixin, GenericAPIView):
    """
    Concrete view for listing a queryset.
    """
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

进一步查看 ListModelMixin 的源码可以看出,它获取到我们传入的 querysetserializer 类,然后将 queryset 传入 serializer 类中,最后调用 Response 将数据返回(先忽略 page 这是分页的内容),这和我们最初的写法也没有太大的区别

class ListModelMixin(object):
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

再来看看 generics.GenericAPIView 都做了些什么

下面是 GenericAPIView 的源码(不全,暂时先看我们需要的部分),可以看出它是继承自 APIView

而且 ListModelMixin 中获取 querysetserializer 类的方法就定义在这里面

它先检验了是否存在 querysetserializer_clas 两个类变量,如果有再进行一次类型检测,并且对数据进一步封装

class GenericAPIView(views.APIView):
    queryset = None
    serializer_class = None
    
    def get_queryset(self):
        assert self.queryset is not None, (
            "'%s' should either include a `queryset` attribute, "
            "or override the `get_queryset()` method."
            % self.__class__.__name__
        )
        queryset = self.queryset
        if isinstance(queryset, QuerySet):
            # Ensure queryset is re-evaluated on each request.
            queryset = queryset.all()
            return queryset

        def get_serializer(self, *args, **kwargs):
            serializer_class = self.get_serializer_class()
            kwargs['context'] = self.get_serializer_context()
            return serializer_class(*args, **kwargs)

        def get_serializer_class(self):
            assert self.serializer_class is not None, (
                "'%s' should either include a `serializer_class` attribute, "
                "or override the `get_serializer_class()` method."
                % self.__class__.__name__
            )
            return self.serializer_class

        def get_serializer_context(self):
            return {
                'request': self.request,
                'format': self.format_kwarg,
                'view': self
            }

Viewsets和Router

rest_framework 还提供了一个 viewsets 模块

这个类主要的功能就是它重写了 as_view 方法他可以使我们处理请求变得更加简单

以下代码我们只是修改了继承关系,而未修改其他,然而在上面对 ListModelMixin 源码的分析中,我们得知它具有一个 list 方法,但是这里我们没有继承 ListAPIView ,所以没有人来调用 list 方法,就无法返回数据

这时候就体现了 viewsets 重写 as_view 方法的精妙之处

from rest_framework import mixins, viewsets
from rest_framework.pagination import PageNumberPagination

from .serializers import GoodsSerializer
from .models import Goods


class GoodListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
    """
    商品列表页
    """
    queryset = Goods.objects.all()
    serializer_class = GoodsSerializer

重写url路由映射,这里我们调用 GoodListViewSetas_view 方法,将 get 请求映射到 list 方法上,当请求以 get 方式发送过来的时候,就会自动的调用 list 方法

from django.urls import path

from .views import GoodListViewSet


app_name = 'goods'

goods_list = GoodListViewSet.as_view({'get': 'list'})

urlpatterns = [
    path('list/', goods_list, name='goods_list'),
]

使用 Router 重写路由映射

使用方式,实例化一个 router 对象,调用其 register 方法,将路由映射注册到 router 中,然后在 urlpatterns 中引入 router.urls ,它就会自动的帮我们完成请求和方法的绑定

from django.urls import path, include
from rest_framework.routers import DefaultRouter

from .views import GoodListViewSet


app_name = 'goods'

router = DefaultRouter()
router.register('list', GoodListViewSet)

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

pagination

关于上面提到的分页操作,要实现也很简单

只需要在 REST_FRAMEWORK 配置文件中添加分页配置,如下,这里我们指定每一页10条数据

# Pagination
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,

实际上在添加了分页配置后,REST框架还帮我们完成了很多额外的功能,比如说它将数据总的条数,上一页和下一页的地址都放在了返回的数据中,为图片和文件类型的数据自动加上了访问域名等

内置的分页类型有:

  • PageNumberPagination
  • LimitOffsetPagination
  • CursorPagination

具体使用方式参照 API 文档,这里不过多介绍

当然也可以为每个视图设置单独的 pagination_class ,从上面的 mixins.ListModelMixingenerics.GenericAPIView 的源码分析中,我们已经得知,它可以加载一个 pagination_class

如下面的代码中,我们定义了一个自己的 pagination_class 然后在 GoodListView 使用了它,要想在全局中使用我们自己定义的 pagination_class 只需要替换全局配置文件中的 'DEFAULT_PAGINATION_CLASS' 即可

from rest_framework import generics
from rest_framework.pagination import PageNumberPagination

from .serializers import GoodsSerializer
from .models import Goods


class GoodsPagination(PageNumberPagination):
    page_size = 5
    page_size_query_param = 'page_size'
    page_query_param = 'p'
    max_page_size = 10


class GoodListView(generics.ListAPIView):
    """
    商品列表页
    """
    queryset = Goods.objects.all()
    serializer_class = GoodsSerializer
    pagination_class = GoodsPagination

同样在的 ViewSet 是继承自 APIView 的,所以也可以使用该属性,来实现分页功能

Request

REST framework的 Request 类扩展了标准的 HttpRequest ,添加对REST framework的灵活请求解析和请求身份验证的支持

请求解析:

  • data: request.data 返回请求正文的解析内容,它可以获取标准的 request.POST 和 request.FILES 内容
  • query_params: request.query_params可以获取 request.GET 的数据
  • parsers: request.parsers 可以解析用户发送过来的数据类型

用户认证:

  • user: request.user 通常代表当前登录的用户对象
  • auth: request.auth 返回任何其他身份验证上下文

Reponse

REST framework 通过提供一个 Response 类来支持 HTTP content negotiation,该类允许你返回可以呈现为多种内容类型的内容,具体取决于客户端的请求

与常规的 HttpResponse 对象不同,你不能使用渲染内容来实例化一个 Response 对象,而是传递未渲染的数据,包含任何Python基本数据类型。

Response 类使用的渲染器无法自行处理像 Django model 实例这样的复杂数据类型,因此你需要在创建 Response 对象之前将数据序列化为基本数据类型。

你可以使用 REST framework的 Serializer 类来执行此类数据的序列化,或者使用你自定义的来序列化

Response的参数:

  • data: response的数列化数据.
  • status: response的状态码。默认是200. 另行参阅 status codes.
  • template_name: HTMLRenderer 选择要使用的模板名称。
  • headers: A dictionary of HTTP headers to use in the response.
  • content_type: response的内容类型。通常由渲染器自行设置,由content negotiation确定,但是在某些情况下,你需要明确指定内容类型

第三章:过滤层

REST framework列表视图的默认行为是返回一个model的全部queryset,通常你却想要你的API来限制queryset返回的数据

最简单的过滤任意 GenericAPIView 子视图queryset的方法就是重写它的 get_queryset() 方法

重写这个方法允许你使用很多不同的方式来定制视图返回的queryset,但我们也可以使用第三方库来实现过滤

FilterBackend

django-filter 库包含一个为REST framework提供高度可定制字段过滤的 DjangoFilterBackend

要使用 DjangoFilterBackend,首先要先安装 django-filter

pip install django-filter

加入到配置文件的 INSTALLED_APPS

INSTALLED_APPS = [
    ...
    'django_filters',

现在,你需要在settings中添加过滤类(该配置是一个全局配置,会作用在所有视图上):

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

或者你也可以将过滤类添加到一个单独的 viewviewSet 中:

  • filter_backends: 指定过滤类(注意需要接收一个元组)
  • filter_fields:指定过滤的字段(该字段必须和queryset中的字段对应)
from django_filters.rest_framework import DjangoFilterBackend

class GoodListViewSet(generics.ListAPIView):
    ...
    filter_backends = (DjangoFilterBackend, )
    filter_fields = ('name', 'shop_price')

FilterSet

上面的方法只能匹配完全相等的数据,对应模糊匹配和区间匹配等复杂的过滤逻辑无法实现

我们可以自定义一个Filter类,该类需要继承 django_filters.rest_framework.FilterSet

从下面的代码中可以看出它也具有一个内部Meta类,而且用法和ModelSerializer类似

  • model:指定模型类
  • fields:指定过滤的字段,我们也可以自己定义字段,最后都需要加入到这个列表中

自定义字段的参数说明:

  • field_name:指定在Model中对应的字段名,我们基于该字段做操作
  • lookup_expr:过滤操作(内置的过滤操作和Django的Model的查询集类似)
  • method:自定义过滤函数

自定义过滤函数参数说明:

  • queryset:查询对象
  • value:传入的值
import django_filters
from django.db.models import Q
from .models import Goods


class GoodsFilter(django_filters.rest_framework.FilterSet):
    """
    商品的过滤器
    """
    pricemin = django_filters.NumberFilter(field_name='shop_price', lookup_expr='gte')
    pricemax = django_filters.NumberFilter(field_name='shop_price', lookup_expr='lte')
    top_category = django_filters.NumberFilter(method='top_category_filter')

    def top_category_filter(self, queryset, name, value):
        queryset = queryset.filter(Q(category_id=value) |
                                   Q(category__parent_category_id=value) |
                                   Q(category__parent_category__parent_category_id=value))
        return queryset

    class Meta:
        model = Goods
        fields = ['pricemin', 'pricemax', 'top_category', 'is_hot']

**注:**创建好 Filter 类后,还需要通过 filter_class 属性将它配置到视图中 filter_class = GoodsFilter

from django_filters.rest_framework import DjangoFilterBackend
from .filters import GoodsFilter

class GoodListViewSet(generics.ListAPIView):
    ...
    filter_backends = (DjangoFilterBackend, )
    filter_class = GoodsFilter

SearchFilter

SearchFilter 类支持基于简单单查询参数的搜索

仅当view中设置了 search_fields 属性时,才应用 SearchFilter

search_fields 属性应该是model中文本类型字段的名称列表,例如 CharFieldTextField

from rest_framework.filters import SearchFilter

class GoodListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
	
	...
    filter_backends = (SearchFilter, )
    search_fields = ('name', 'goods_brief', 'goods_desc')

**注:**该类不是使用的 django_filter 这个第三方库,而是 rest framework 框架提供

这将允许客户端通过进行以下查询来过滤列表中的项目:

http://example.com/api/users?search=russell

你还可以在查找API中使用双下划线符号对ForeignKey或ManyToManyField执行相关查找:

search_fields = ('username', 'email', 'profile__profession')

默认情况下,搜索将使用不区分大小写的部分匹配, 搜索参数可以包含多个搜索项,其应该是空格和/或逗号分隔, 如果使用多个搜索术语,则仅当所有提供的术语都匹配时才在列表中返回对象

可以通过在 search_fields 前面添加各种字符来限制搜索行为

  • ‘^’ 以指定内容开始
  • ‘=’ 完全匹配
  • ‘@’ 全文搜索(目前只支持Django的MySQL后端)
  • ‘$’ 正则搜索

例如:

search_fields = ('=username', '=email')

默认情况下,搜索参数名为 'search',但这可以通过使用 SEARCH_PARAM 设置覆盖

OrderingFilter

OrderingFilter 类支持简单的查询参数控制结果排序

默认情况下,查询参数名为 'ordering',但这可以通过使用 ORDERING_PARAM 设置覆盖

from rest_framework.filters import OrderingFilter

class GoodListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):

	...
    filter_backends = (OrderingFilter, )
    ordering_fields = ('sold_num', 'shop_price',)

**注:**该类不是使用的 django_filter 这个第三方库,而是 rest framework 框架提供

如果不在视图上指定 ordering_fields 属性,过滤器类将默认允许用户对 serializer_class 属性指定的serializer上的任何可读字段进行过滤

如果你确信视图正在使用的queryset不包含任何敏感数据,则还可以通过使用特殊值 '__all__' 来明确指定允许对任何model字段或queryset进行排序

如果在view中设置了 ordering 属性,则将把它用作默认排序

通常,你可以通过在初始queryset上设置 order_by 来控制此操作,但是使用view中的 ordering 参数允许你以某种方式指定排序,然后可以将其作为上下文自动传递到呈现的模板。如果它们用于排序结果的话就能使自动渲染不同的列标题成为可能

ordering 属性可以是字符串或字符串的列表/元组

第四章:综合篇

用户认证

认证的简介

认证是在传入请求的时候附带一组识别凭据,例如请求来自的用户或与其签名的 Token ,然后,权限限制 可以使用这些凭据来确定是否应允许该请求

验证始终在视图的最开始进行

request.user 属性通常被设置为 contrib.auth 包中 User 类的一个实例,也可以是我们扩展后的 User

request.auth 属性用于任何其他身份验证信息,例如,它可以用于表示请求签名的 Token


认证的方式

认证方案是一个类的列表,REST framework 将尝试使用列表中的每个类进行身份验证,并使用成功完成验证的第一个类的返回值设置 request.userrequest.auth

如果没有类进行验证,request.user 将被设置成 django.contrib.auth.models.AnonymousUser 的实例,request.auth 将被设置成 None

未认证请求的 request.userrequest.auth 的值可以使用 UNAUTHENTICATED_USERUNAUTHENTICATED_TOKEN 设置进行修改


认证的配置

可以使用 DEFAULT_AUTHENTICATION_CLASSES 设置全局的默认身份验证方案。比如:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        # 实际上不配置,它也会默认使用这两个验证类
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    )
}

也可以基于单独的视图进行身份验证

from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework import mixins, viewsets


class UserFavViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
    authentication_classes = (SessionAuthentication, BasicAuthentication)
    permission_classes = (IsAuthenticated,)

BasicAuthentication

此认证方案使用HTTP 基本认证,针对用户的用户名和密码进行认证(基本认证通常只适用于测试)

如果认证成功 BasicAuthentication 提供以下信息。

  • request.user 将是一个 Django User 实例
  • request.auth 将是 None

那些被拒绝的未经身份验证的请求会返回使用适当 WWW-Authenticate 标头的 HTTP 401 Unauthorized 响应例如:

WWW-Authenticate: Basic realm="api"

**注:**如果你在生产中使用 BasicAuthentication,那么你必须确保你的API仅在 https 中可用

你还应确保你的API客户端始终在登录时重新请求用户名和密码,并且不会将这些详细信息存储到持久存储中


SessionAuthentication

此认证方案使用Django的默认session后端进行身份验证,Session身份验证适用于与你的网站在相同的Session环境中运行的AJAX客户端(因此在前后端分离的系统中,很少用到,适用于前后端分离系统的网站后台管理页面)

如果成功验证,SessionAuthentication 提供以下凭据

  • request.user 是一个 Django User 实例
  • request.authNone

那些被拒绝的未经身份验证的请求会返回 HTTP 403 Forbidden 响应

如果你正在使用带有 SessionAuthentication 的AJAX样式的API,你需要确保任何不安全的HTTP方法调用(如:PUT, PATCH, POST or DELETE请求)都包含有效的CSRF token

警告:在创建登陆页面时,始终要使用Django的标准登陆视图

这样才能确保你的登陆视图被正确的认证保护

由于需要同时支持session和non-session非会话身份验证,REST框架中的CSRF验证与标准Django中的工作方式略有不同。这意味着只有经过身份验证的请求需要CSRF令牌,匿名请求可能不会发送CSRF令牌tokens,此行为不适用于始终需要使用CSRF验证的登录视图


TokenAuthentication

该认证方案使用简单的基于 Token 的HTTP认证方案,适用于客户端 - 服务器或者前后端分离的系统

要使用 TokenAuthentication 方案,你需要配置认证类,以便包含 TokenAuthentication,另外在INSTALLED_APPS 设置中还需要包含 rest_framework.authtoken

INSTALLED_APPS = (
    ...
    'rest_framework.authtoken'
)

注: 确保在修改设置后运行一下 manage.py migraterest_framework.authtoken app 会提交一些Django数据库迁移操作,为我们创建一张数据表

每个用户都需要有一个唯一的 Token ,而这个 Token 是需要我们自己创建的,创建方式如下:

from rest_framework.authtoken.models import Token

token = Token.objects.create(user=...)

前面我们所过,它会为我们创建一张数据表,我们就可以通过调用这张表的 create() 方法,传入一个 User 对象,来创建一个 Token ,之后我们就可以使用该 Token 来进行身份验证了

但是在前后端分离的项目中,我们往往不能只通过后台逻辑来实现 Token 的创建,而是通过暴露一个API给前端,让他通过访问API来获取用户对应的 Token ,方法也很简单,如下:

from rest_framework.authtoken import views
urlpatterns += [
    path('api-token-auth/', views.obtain_auth_token)
]

配置好这个URL后只需要通过 post 请求中携带 usernamepassword 属性,通过验证后,它就会帮我们自动的生成一条 Token 返回,并且保存到数据库

获取到 Token 信息后,只需要在之后的请求中的请求头里包含 Authorization 属性,值应该是以字符串"Token"为前缀,以空格分割的两个字符串(后面的是刚才请求API返回的字符串)例如:

Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b

这样发送请求,后台就能获取到对应的用户了


Json Web Token

在上面提到的 TokenAuthentication 中,其实存在几个问题:

  • Token 依然要在服务器端保存一份,这样加大了服务器内存压力
  • 自带的 Token 过于简单,没有过期时间等,不够安全

所以在前后端分离的系统中,我们更多的是使用 JWT 的验证方式

JWT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。它具备两个特点:

  • 简洁(Compact):可以通过URL, POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快
  • 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库

JWT 由三部分组成:

  • Header:头部包含了两部分,token 类型和采用的加密算法
{
  "alg": "HS256",
  "typ": "JWT"
}
  • Payload :这部分就是我们存放信息的地方了,你可以把用户 ID 等信息放在这里
{	
    "iss": "lion1ou JWT",     // 签发者
    "iat": 1441593502,        // 签发时间
    "exp": 1441594722,        // 过期时间
    "aud": "www.example.com", // 接收方
    "sub": "lion1ou@163.com"  // 面向的用户
}
  • Signature :对数据进行签名

前面两部分都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥(保存在服务器),然后使用 header 中指定的签名算法(HS256)进行签名,签名的作用是保证 JWT 没有被篡改过

**警告:**前面两部分都是可以轻松解开的,所以不能存放敏感信息


认证过程如下:

可以看出 JWT 没有在服务器存储任何数据(除了密钥),每次请求都是通过算法来验证,所以这样就大大的降低的服务器的存储压力


使用方式和上面的 TokenAuthentication 类似,不过它依赖一个第三方的库

安装第三方库 djangorestframework-jwt

pip install djangorestframework-jwt

配置验证类,同样的可以全局下配置,也可以在单独的视图下配置

全局配置需要在 settings 中添加

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        ...
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),
}

视图中配置

from rest_framework import mixins, viewsets
from rest_framework_jwt.authentication import JSONWebTokenAuthentication


class UserFavViewSet(..., viewsets.GenericViewSet):
	
    ...
    authentication_classes = (JSONWebTokenAuthentication, )

替换我们获取 Token 的URL

from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
    path('api-token-auth/', obtain_jwt_token),
]

JWT 还有一些配置文件需要添加到 settings

JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 过期时间
    'JWT_AUTH_HEADER_PREFIX': 'JWT',                    # 字符串前缀
}

配置完成之后,我们通过 post 方式携带用户名和密码请求URL时,它就会给我们返回一个 Token

{
"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6Imx1b3lhbmciLCJleHAiOjE1MzUzNDMwMDEsImVtYWlsIjoiIn0.OfYGQtA2ut7YYVS7KmrNKIHSbYMohoixRx28XqUOhfs"
}

获取到 Token 信息后,只需要在之后的请求中的请求头里包含 Authorization 属性,值应该是以字符串"JWT"为前缀,以空格分割的两个字符串(后面的是刚才请求API返回的字符串)例如:

Authorization: JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6Imx1b3lhbmciLCJleHAiOjE1MzUzNDMwMDEsImVtYWlsIjoiIn0.OfYGQtA2ut7YYVS7KmrNKIHSbYMohoixRx28XqUOhfs

这样发送请求,后台就能获取到对应的用户了

权限管理

权限的简介

权限检查始终在视图的开始处运行,在允许继续执行任何其他代码之前运行, 权限检查通常会使用request.userrequest.auth 属性中的身份验证信息来确定是否允许传入请求

权限用于授予或拒绝将不同类别的用户访问到API的不同部分

最简单的权限是允许访问任何经过身份验证的用户,并拒绝访问任何未经身份验证的用户。这对应于REST框架中的 IsAuthenticated

REST框架中的权限始终被定义为一个权限类的列表

在运行视图的主体之前,检查列表中的每个权限,如果任何权限检查失败,会抛出一个exceptions.PermissionDeniedexceptions.NotAuthenticated 异常,并且视图的主体将不会运行

内置权限

  • AllowAny 权限类将允许不受限制的访问

此权限不是严格要求的,因为你可以通过使用空列表或元组进行权限设置来获得相同的结果,但你可能会发现指定此类很有用,因为它使意图更明确


  • IsAuthenticated 权限类将拒绝任何未经身份验证的用户的权限

如果你希望你的API仅供注册用户访问,则此权限适用


  • IsAdminUser 只允许管理员访问

除非user.is_staffTrue,否则IsAdminUser权限类将拒绝任何用户的权限,如果你希望你的API只能被部分受信任的管理员访问,则此权限是适合的


  • IsAuthenticatedOrReadOnly 将允许经过身份验证的用户执行任何请求。只有当请求方法是“安全”方法(GET, HEADOPTIONS)时,才允许未经授权的用户请求

如果你希望你的API允许匿名用户读取权限并且只允许对已通过身份验证的用户进行写入权限,则此权限是适合的

自定义权限

自定义权限,需要继承 BasePermission 并实现以下方法中的一个或两个

  • has_permission(self, request, view) :视图级别的检测
  • has_object_permission(self, request, view, obj) :实例级别的检测

这两个方法最终都需要返回 True 或者 False 来指明请求是否通过

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
   def has_object_permission(self, request, view, obj):
       if request.method in permissions.SAFE_METHODS:
           return True
       return obj.user == request.user
标签: RESTAPIDRF
添加评论
评论列表
没有更多内容