4 minute read

认证

通过继承BaseAuthentication,并实现authenticate方法,可以实现自定义的认证函数

# authentication.py

from rest_framework import authentication

class MyAuthentication(authentication.BaseAuthentication):

    def authenticate(self, request):
        try:
            # programing your logic code here...
        except:
            raise exceptions.AuthenticationFailed("用户认证失败")

    # 认证失败,返回请求头信息
    def authenticate_header(self, request):
        pass

返回值:

  • None: 该认证不做处理
  • raise exception.AuthenticationFailed(“用户认证失败”)
  • (user, obj) (用户,模型对象自身)这两个变量将会赋值给request.user, request.auth

全局配置

settings.py中配置REST_FRAMEWORK

# settings.py

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [MyAuthentication,],
}

具体配置可参考rest_framework.settings.api_settings.DEFAULTS

局部配置

如果使用了全局配置,那么对于所有视图函数都会有效,对于不需要认证的函数,可以在视图中设置authentication_classes属性,为空表示该视图不做认证配置。

# views.py

from path.to.authentication import MyAuthentication

class CustomAPIView:
    authentication_classes = [MyAuthentication,]

JWT实现登录认证

JWT(JSON Web Token)相比传统的token认证优势在于:

  • 可以设置token的过期时间,安全性更高
  • 减轻数据请求查询token带来的数据库压力

JWT的请求周期

  1. 客户端尝试登录操作,将验证信息发送给服务端。
  2. 服务端进行后台验证,生成jwt返回。
  3. 前端获取到jwt后保存在本地,之后的数据请求都携带jwt。

在Django中使用

安装jwt依赖包

pip install djangorestframework-jwt

配置相关代码

# settings.py

REST_FRAMEWORK = {
    "DEFAULT_PERMISSION_CLASSES": (
        'rest_framework.permissions.IsAuthenticated',
    ),
    "DEFAULT_AUTHENTICATION_CLASSES": (
        "rest_framework_jwt.authentication.JSONWebTokenAuthentication",
    )
}

import datetime
# 配置token的有效期
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}

配置jwt的路由

# urls.py

from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
    ...
    path("jwt-token-auth/", obtain_jwt_token),
]

启动项目,以POST方法访问http://127.0.0.1/jwt-token-auth/,携带用户验证信息

{
    "username":"admin",
    "password":"admin"
}

网页将返回token值:

{
    "token": "dfhasjkfhgasdilfndisuafhasdlifbdsahfhasdjkvfadsfhdsa"
}

创建一个视图类,用于实现GET请求访问

# views.py
from rest_framework.views import APIView
from rest_framewort.response import Response

class IndexAPIView(APIView):
    def get(self, request, *args, **kwargs):
        return Response("首页")

为视图配置路由,此处略。

然后以GET的方式访问网页,在请求头中加入token信息:

{
    "Authorization": "JWT dfjasjfghdaslnfdsakhflasdihfkadsfadskj"
}

获取到首页信息,也即表示通过了验证。

权限

继承BasePermission,并实现has_permission方法,可以实现自定义的权限函数

# permissions.py

from rest_framework import permissions

class MyPermissions(permissions.BasePermission):

    def has_permission(self, request, view):
        # programing your logic code here...

返回值:

  • True
  • False

BasePermission类中还有一个has_object_permission方法,是对象级别的权限认证。

全局配置

```python

settings.py

REST_FRAMEWORK = { … “DEFAULT_PERMISSION_CLASSES”: [MyPermission, ], }


## 局部配置
在视图函数中定义*permission_classes*属性
```python
# views.py

from path.to.permission import MyPermission

class CustomAPIView:
    permission_classes = [MyPermission, ]

注意:认证与权限应当同时添加(即使你没有设置认证),否则当权限通不过时,报错信息将只会显示认证未通过。

节流

控制访问频率

通过继承BaseThrottle,并实现allow_requestwait方法

# throttle.py

from rest_framework import throttling

class MyThrottle(throttling.BaseThrottle):

    def allow_request(self, request, view):
        # programing your logic code here...
        return True

    def wait(self):
        # 返回一个剩余等待的数字
        return 10

返回值

  • True
  • False

全局配置

settings.py中配置

# settings.py

REST_FRAMEWORK = {
  "DEFAULT_THROTTLE_CLASSES": [MyThrottle, ]
}

局部配置

# views.py

from path.to.throttle import MyThrottle

class CustomAPIView:
    throttle_classes = [MyThrottle, ]

序列化器

序列化器允许queryset和模型实例等复杂的数据类型转化为python数据类型,并且能够简单地被渲染。

Serializer

定义一个简单模型

# models.py

class CustomModel(models.Model):
    email = models.EmailField()
    content = models.CharField()
    created = models.DateTimeField()

django的每个app新建serializers.py

# serializers.py

from rest_framework import serializers

class CustomSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField()
    created = serializers.DateTimeField()

为数据库插入一些测试数据后,在shell中测试

>>> from app.models import CustomModel
>>> from app.serializers import CustomSerializer
>>> custom = CustomModel.objects.all()
>>> serializer = CustomSerializer(instance=custom, many=True)
>>> serializer.data
# [OrderedDict([('email', 'test@example.com'), ('content', 'hello world'), ('created', '2020-02-18T22:52:13.398851Z')])]

字段

serializer.data中展示的字段,便是CustomSerializer中定义的字段,所以CustomSerializer中定义的字段必须在模型中存在。

对于一些特殊的字段,例如ForeignKey,可以添加source参数指定关联的模型。这样可以取到外键字段关联的字符串值,而非单纯的对象本身

owner = serializers.CharField(source="owner.username")

源码中对source的处理方式是:如果owner.username是一个可调用对象,那么将返回owner.username()的结果,否则直接返回owner.username,所以source也可以填方法名,比如get_absolute_url

同理,对于ManyToManyField字段,可以使用field.all,但是这样的展示,只是将关联的模型对象展示出来,如果想要详细到字段的显示,可以通过自定义显示的方法。

SerializerMethodField

SerializerMethodField字段可以自定义数据的展示,需要实现一个get_fieldname方法

many = serializers.SerializerMethodField()

def get_many(self, row):
    '''
    函数名以get_开头,以字段名结尾
    '''

    results = row.many.all()
    
    ret = []
    for item in results:
        ret.append({"id":item.id, "name":item.name})

    return ret
    # return [
        {"id":1, "name":"karfka"},
        {...}
    ]

关于字段的扩展参数,可以参考官网Serializer Fields

ModelSerializer

自定义的序列化器类还可以继承ModelSerializer,相对有几个优点:

  • 自动生成模型中存在的字段
  • 自动为序列化器生成验证器
  • 包含了简单的.create()和.update()的实现
# serializers.py

from rest_framework import serializers

class CustomSerializer(serializers.ModelSerializer):
    class Meta:
        model = CustomModel
        fields = "__all__"
        read_only_fields = ["email"]    # 某个字段只读
        exclude = ["id"]    # 排除某个字段
        depth = 1   # 嵌套序列化,可以得到模型的关系型字段(比如ForeignKey)关联的其他模型的信息,深度为1,建议0-10

shell中导入类,打印对象的相关信息

>>> from app.serializers import CustomSerializer
>>> custom = CustomSerializer()
>>> custom
CustomSerializer():
    id = IntegerField(label='ID', read_only=True)
    email = EmailField(max_length=254)
    content = CharField(max_length=200)
    created = DateTimeField(required=False)

ModelSerializer类依然可以定义模型中不存在的字段,或者覆盖原有字段,或者自定义展示的字段。任何关系型字段,例如ForeignKeyField将会默认映射到PrimaryKeyRelatedField

HyperlinkedModelSerializer

HyperlinkedModelSerializer使用超链接来表示关系,而非ModelSerializer使用的PrimaryKeyRelatedField

默认情况下,序列化器将包含一个url字段。或者,你可以使用模型中的ForrignKey字段,在serializer类中使用HyperlinkedIdentityField重写该字段。这个字段将会直接返回一个拼接好的可访问的url,前提是你已经定义好该url的路由映射和视图函数。

# serializers.py

from rest_framework import serializers
from app.models import CustomModel

class CustomSerializer(serializers.HyperlinkedModelSerializer):
    url = serializers.HyperlinkedIdentityField(
            view_name="detail",
            lookup_field='pk',
        )

    class Meta:
        model = CustomModel
        fields = ["url", "id", "email", "content"]

view_name表示url映射中对应的namelookup_field表示通过pk关键字去寻找对应的实例。

当定义了HyperlinkedIdentityField,那么在实例化CustomSerializer时,必须将request添加到对象的上下文中去,确保生成完整的url

serializer = CustomSerializer(isntance=model, context={'request': request})

验证

发送POST请求时,可以在保存数据之前先对数据进行验证。

serializer = CustomSerializer(data=data)
if serializer.is_valid():
    return serializer.validated_data
else:
    return serializer.errors

serializers.errors的错误信息可以定制

# serializers.py

class CustomSerializer(serializers.Serializer):
    content = serializers.CharField(error_message={"required": "内容不能为空"})

字段级的验证

通过在CustomSerializer中实现validate_fieldname方法,可以对单独的字段进行验证,类似于Djangoform表单中的clean_fieldname方法

# serializers.py

class CustomSerializer(serializers.Serializer):
    password = serializers.CharField()

    def validate_password(self, value):
        if value != "*****":
            raise serializers.ValidationError("password didn't match")
        return value

对象级的验证

如果验证需要多个字段,可以在CustomSerializer类中实现validate方法

# serializers.py

class CustomSerializer(serializers.Serializer):
    password = serializers.CharField()
    passwordrepeat = serializers.CharField()

    def validate(self, data):
        """
        :type data: dict
        """
        if data["password"] != data["passwordrepeat"]:
            raise serializers.ValidationError("password inconsistent")
        return data

Validators

可以为单个字段添加多个自定义验证,验证也可重用

class CustomValidation:

    def __init__(self, title):
        self.title = title

    def __call__(self, value):
        if not self.value.startswith(self.title):
            message = "title must start with %s" % self.value
            raise serializers.ValidationError(message)

接下来在字段中引用

# serializers.py

class CustomSerializer(serializers.ModelSerializer):
    title = serializers.CharField(validators=[CustomValidation("ism_")])

通用视图

APIView

APIView继承自DjangoView,在此基础上增加了很多属性。

class APIView(View):

    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

GenericAPIView

GenericAPIView继承自APIView,是其他视图的通用基本视图。同样定义了一些属性以及常用方法

class GenericAPIView(APIView):
    # 该属性不应该在方法中直接使用,而是调用`get_queryset()`获取
    queryset = None
    
    serializer_class = None

    lookup_field = 'pk'
    lookup_url_kwargs = None

    filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
    
    pagination_class = api_settings.DEFAULT_PAGINATION_CLASS

    # 常用方法
    def get_queryset(self):...

    def get_object(self):...

    def get_serializer(self, *args, **kwargs):...

    ...

mixins

mixins中定义了用于基本视图中的行为方法,即是常见的对querysetCURD操作。mixins一共提供了五种行为操作的类。

  • ListModelMixin:提供list方法,返回列表类型的结果集
  • CreateModelMixin:提供create方法,创建和保存新的模型实例
  • RetrieveModelMixin:提供retrieve方法,返回一个详细的模型实例信息
  • UpdateModelMixin:提供update方法,修改和保存已有模型实例
  • DestoryModelmixin:提供delete方法,删除模型实例

以下是一些通过继承mixins提供的一个或多个类以及通用视图GenericAPIView得到的视图函数。

  • CreateAPIView
  • ListAPIView
  • RetrieveAPIView
  • DestroyAPIView
  • UpdateAPIView
  • ListCreateAPIView
  • RetrieveUpdateAPIView
  • RetrieveDestroyAPIView
  • RetrieveUpdateDestroyAPIView

ViewSet(视图集)

ViewSet指将一系列相关视图组合在一个单个类中,形成一套处理逻辑。ViewSet不提供处理函数如getpost,只提供动作函数如listcreate

# views.py

from rest_framework import viewsets

class CustomViewSet(viewsets.ViewSet):
    def list(self, request):
        # return list of queryset

    def retrieve(self, request, pk=None):
        # return detail of one model instance

ViewSet类继承自ViewSetMixinAPIViewViewSetMixin中对as_view方法做了修改,接收action参数,根据参数来分发给目标动作函数。

custom_list = CustomViewSet.as_view(actions={"get":"list"})
custom_detail = CustomViewSet.as_view(actions={"get":"retrieve"})

注意:通常情况下,我们不会这样做,而是向路由系统注册ViewSet,让url自动生成。这会在路由一节中介绍

常用的视图集类:

  • ViewSet
  • GenericViewSet
  • ModelViewSet
  • ReadOnlyModelViewSet

action装饰器

如果你有特别的方法也需要路由,那么可以为其添加action装饰器

# views.py

class CustomViewSet(ViewSet):

    @action(detail=False, methods=["get"])
    def another_queryset(self, request):
        # return queryset

detail=False表示该方法返回一个结果集,不然就是返回一个对象。

然后同样为其分发一个动作函数

another = CustomViewSet.as_view(actions={"get":"another_queryset"})

路由

在使用视图集时,需要对路由进行修改,根据使用的请求方式不同、动作函数不同等原因,一个视图类有时需要分发多个路由

# urls.py

custom_list = views.CustomViewSet.as_view({
    "get": "list",
    "post": "create"
})

custom_detail = views.CustomViewSet.as_view({
    "get": "retrieve",
    "put": "update",
    "delete": "destroy"
})

urlpatterns = [
    path("customs/", custom_list, name="custom-list"),
    path("custom/<int:pk>/", custom_detail, name="custom-detail")
]

DefaultRouter

通过注册视图类来自动生成路由

# urls.py

from rest_framework import routers

router = routers.DefaultRouter()
router.register(r'customs/', views.CustomViewSet)

urlpatterns = router.urls

它将会自动生成所有的url。

DefaultRouter会自动生成一个api-root view,里面包括了所有列表视图的超链接;另外还支持.json后缀的url

版本

版本控制允许在不同的客户端定制不同的行为。

全局配置

# settings.py

REST_FRAMEWORK = {
    "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.QueryParameterVersioning"
    
    # Optional
    "DEFAULT_VERSION": None
    "ALLOWED_VERSIONS": None
    "VERSION_PARAM": "version"
}

rest_framework提供几种常用的版本控制方式,源码中有例子

  • QueryParameterVersioning:通过url中的参数控制版本
  • AcceptHeaderVersioning:通过将版本添加到请求头的方式
  • URLPathVersioning:通过url路径
  • NamespaceVersioning:通过django的namespace
  • HostNameVersioning:子域名

局部配置

通过在视图中定义versioning_class属性。

# views.py

class CustomView(APIView):
    versioning_class = versioning.URLPathVersioning

注意:单独设置并不推荐,全局的版本控制应该是单一的。

解析器

解析器允许请求附带各种数据类型,如jsonform等。解析器会检查传入的请求的Content-Type头,然后使用对应的解析器解析,内存将保存在request.data属性中。

全局配置

# settings.py

REST_FRAMEWORK = {
    "DEFAULT_PARSER_CLASSES": [
        'rest_framework.parsers.JSONParser',
    ]
}

更多解析类请阅读源码:rest_framework.parsers

局部配置

# views.py

from rest_framework.parsers import JSONParser
from rest_framework.response import Response
from rest_framework.views import APIView

class CustomView(APIView):
    parser_classes = [JSONParser, ]

    def post(self, request, *args, **kwargs):
        return Response({"code":"200", "data":request.data})

分页

全局配置

# settings.py

REST_FRAMEWORK = {
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
    "PAGE_SIZE": 100
}

注意:上面两项配置都是必须的

局部配置

通过在视图函数中定义pagination_class属性。

# views.py

class CustomView(APIView):
    pagination_class = PageNumberPagination

默认的三种分页风格

  • PageNumberPagination 基于页码的分页风格。例如:页码为1,数据为10条。
  • LimitOffsetPagination 基于位置与限量的分页风格。例如:从第10条的位置向后取10条数据。
  • CursorPagination 基于指针的分页风格,只显示上一页和下一页。

自定义的分页类

通过实现定制的分页类,可以定制分页的属性,同时也能继承分页的功能

from rest_framework import pagination

class CustomPagination(pagination.PageNumberPagination):
    page_size = 100

    # url中请求页码的参数
    page_query_param = 'page'

    # url中亲贵每页结果集数量的参数
    page_size_query_param = "number"

    def paginate_queryset(self, queryset, request, view):
        ...

更多的属性配置请参考官方文档:Pagination

在视图中使用

# views.py

from app.models import CustomModel
from path.to.serializers import CustomSerializer
from path.to.pagination import CustomPagination
from rest_framework.response import Response

class CustomView(APIView):
    def get(self, request, *args, **kwargs):
        custom = CustomModel.objects.all()
        
        page = CustomPagination()
        page_result = page.paginate_queryset(queryset=custom, request=request, view=self)

        serializer = CustomSerializer(instance=page_result, many=True)
paginate_queryset

        return Response(serializer.data)