0%

Django REST framework 的那点事

关于 Python 系列其他文章的传送门

  1. 《 Python 基础语法 》

  2. 《 Python 进阶:网络、多线程、异步、数据库、WEB 》

  3. 《 Python 和 RabbitMQ 的那点事 》

  4. 《 Python 和 Redis 的那点事 》

  5. 《 Python 爬虫基础和包 XPath、BeautifulSoup4、PhantomJS、Selenium 》

  6. 《 边爬豆瓣,边学习 Python 爬虫框架 Scrapy 》

  7. 《 用了 Scrapy-Redis 分布式爬虫后,一个能打的都没有! 》

  8. 《 全栈开发入门 JavaScript、React 和 Django 》

  9. 《 Django REST framework 的那点事 》      您当前所在位置



logo

Django 的 FBV、CBV

FBV

1
2
3
def users(request):
user_list = ['alex', 'oldboy']
return HttpResponse(json.dumps((user_list)))

CBV

基于反射实现根据请求方式不同,执行不同的方法。

原理:

  • url -> view 方法 -> dispatch 方法(反射执行其他:GET/POST/DELETE/PUT)

流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 路由
url(r'^students/', views.StudentsView.as_view()),

# 视图
from django.views import View


class StudentsView(View):

def get(self, request, *args, **kwargs):
return HttpResponse('GET')

def post(self, request, *args, **kwargs):
return HttpResponse('POST')

def put(self, request, *args, **kwargs):
return HttpResponse('PUT')

def delete(self, request, *args, **kwargs):
return HttpResponse('DELETE'

继承:多个类共用的功能,为了避免重复编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from django.views import View

class MyBaseView(object):
def dispatch(self, request, *args, **kwargs):
print('before')
ret = super(MyBaseView, self).dispatch(request, *args, **kwargs)
print('after')
return ret

class StudentsView(MyBaseView, View):

def get(self, request, *args, **kwargs):
return HttpResponse('GET')

class TeachersView(MyBaseView, View):

def get(self, request, *args, **kwargs):
return HttpResponse('GET')

Django 中的 csrf

实现原理:

  • 通过中间件的 rocess_view 方法
    • 检查视图是否被 @csrf_exempt(免除 csrf 认证)
    • 去请求体或 cookie 中获取 token

FBV 的 csrf

全局开启 csrf 时,免除指定函数的认证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', # 全站使用 csrf 认证
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

from django.views.decorators.csrf import csrf_exempt


@csrf_exempt # 该函数无需认证
def users(request):
user_list = ['alex', 'oldboy']
return HttpResponse(json.dumps((user_list)))

全站未开启 csrf 时,使指定函数启用认证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware', # 全站不使用 csrf 认证
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

from django.views.decorators.csrf import csrf_protect


@csrf_protect # 该函数需认证
def users(request):
user_list = ['alex', 'oldboy']
return HttpResponse(json.dumps((user_list)))
)

CBV 的 csrf

注意:当使用 CBV 时,要使用 csrf 的 csrf_exempt 装饰器做全局排除时,需要这样使用,否则会无效:

  • 使用 @method_decorator(csrf_exempt),装饰 dispatch 方法。

示例 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator


class StudentsView(View):

@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super(StudentsView, self).dispatch(request, *args, **kwargs)

def get(self, request, *args, **kwargs):
return HttpResponse('GET')

def post(self, request, *args, **kwargs):
return HttpResponse('POST')

def put(self, request, *args, **kwargs):
return HttpResponse('PUT')

def delete(self, request, *args, **kwargs):
return HttpResponse('DELETE')

或装饰在类上,但要指定 name 为 dispatch,效果和示例 1 一样:

示例 2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator


@method_decorator(csrf_exempt, name='dispatch')
class StudentsView(View):

def get(self, request, *args, **kwargs):
print('get 方法')
return HttpResponse('GET')

def post(self, request, *args, **kwargs):
return HttpResponse('POST')

def put(self, request, *args, **kwargs):
return HttpResponse('PUT')

def delete(self, request, *args, **kwargs):
return HttpResponse('DELETE')

Django REST framework 框架

安装:pip3 install djangorestframework

认证

源码流程

  • 定义 CBV,继承 from rest_framework.views import APIView
  • 路由进来匹配到 CBV 类,执行 as_view()
  • 执行 self.dispatch
    • 封装 request,initialize_request
      • 获取定义的认证类(全局 / 局部),通过列表生成时创建对象。
    • initial
      • perform_authentication,身份验证
        request.user(内部循环…)
      • check_permissions,权限
      • check_throttles,限流

内置的认证类:

  1. 认证类,继承(建议):from rest_framework.authentication import BaseAuthentication
  2. 其他认证类:BasicAuthentication,浏览器的基础认证。

使用方式:

  • 自定义一个类: 继承自 BaseAuthentication,并实现 authenticate 方法;
  • authenticate 的返回值含义:
    • None, 我不管了,下一认证来执行。
    • raise exceptions.AuthenticationFailed('用户认证失败') # from rest_framework import exceptions
    • (元素 1,元素 2):元素 1 会赋值给 request.user;元素 2 会赋值给 request.auth

局部使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from rest_framework.views import APIView
from rest_framework.authentication import BasicAuthentication
from rest_framework import exceptions
from django.http import HttpRequest, HttpResponse
import json


# 验证类
# 生产环境时,应放在一个单独的模块内
class MyAuthentication(BasicAuthentication):
def authenticate(self, request):
token = request._request.GET.get('token')
# 获取用户名和密码,去数据校验
if not token:
raise exceptions.AuthenticationFailed('用户认证失败')
return "alex", None

def authenticate_header(self, val):
pass


class DogView(APIView):
# 局部使用
# 指定使用哪个认证模块
authentication_classes = [MyAuthentication,]

def get(self, request, *args, **kwargs):
print(request)
print(request.user)
ret = {
'code': 1000,
'msg': 'xxx'
}
return HttpResponse(json.dumps(ret), status=201)

def post(self, request, *args, **kwargs):
return HttpResponse('创建 Dog')

def put(self, request, *args, **kwargs):
return HttpResponse('更新 Dog')

def delete(self, request, *args, **kwargs):
return HttpResponse('删除 Dog')

全局使用示例:

在 Django 的配置文件中定义:

1
2
3
4
5
6
7
8
REST_FRAMEWORK = {
# 全局使用的认证类
# 列表内指定认证类的路径
"DEFAULT_AUTHENTICATION_CLASSES": ['api.utils.auth.FirstAuthtication', 'api.utils.auth.Authtication',],
# "UNAUTHENTICATED_USER":lambda :"匿名用户" # 这样使用中文不太建议,不如下面这样直接返回 None
"UNAUTHENTICATED_USER": None, # 匿名时,request.user = None
"UNAUTHENTICATED_TOKEN": None, # 匿名时,request.auth = None
}

权限

代码逻辑和权限的基本一致。

不用框架,自己实现时大概是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyPermission(object):

# 认证逻辑
def has_permission(self, request, view):
if request.user.user_type != 3: # 3 为 SVIP
return False
return True

class OrderView(APIView):
"""
订单相关业务 (只有 SVIP 用户有权限)
"""
permission_classes = [MyPermission,]

def get(self, request, *args, **kwargs):
# request.user
# request.auth
ret = {'code': 1000, 'msg': None, 'data': None}
return JsonResponse(ret)

使用框架要求:

  • 类,必须继承:BasePermission,必须实现:has_permission 方法:
  • 返回值:
    • True,有权访问;
    • False,无权访问。
1
2
3
4
5
6
7
8
9
10
from rest_framework.permissions import BasePermission


class SVIPPermission(BasePermission):
message = "必须是 SVIP 才能访问"

def has_permission(self, request, view):
if request.user.user_type != 3: # 3 为 SVIP
return False
return True

局部使用示例:

1
2
3
4
5
6
7
8
9
10
class UserInfoView(APIView):
"""
订单相关业务(普通用户、VIP)
"""

# 指定要使用的权限模块
permission_classes = [MyPermission1,]

def get(self, request, *args, **kwargs):
return HttpResponse('用户信息')

全局使用示例:

1
2
3
4
5

# 列表内指定认证类的路径
REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES": ['api.utils.permission.SVIPPermission']
}

控制访问频率(节流)

控制指定的秒数内的访问次数。

普通实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import time

# 可以放到 Django 缓存或 Redis 中
VISIT_RECORD = {}


class VisitThrottle():
"""60s 内只能访问 3 次"""

def __init__(self):
self.history = None

def allow_request(self, request, view):
# 1. 获取用户 IP
remote_addr = request.META.get('REMOTE_ADDR')

ctime = time.time()
if remote_addr not in VISIT_RECORD:
VISIT_RECORD[remote_addr] = [ctime, ]
return True
history = VISIT_RECORD.get(remote_addr)
self.history = history

while history and history[-1] < ctime - 60:
history.pop()

if len(history) < 3:
history.insert(0, ctime)
return True

# return True # 表示可以继续访问
# return False # 表示访问频率太高,被限制

def wait(self):
"""
还需要等多少秒才能访问
:return:
"""
ctime = time.time()
return 60 - (ctime - self.history[-1])

继承实现

SimpleRateThrottle 这个自带类帮你实现了上面 allow_requestwait 中的访问频次限制和剩余时间提醒功能。

先在 setting 中定义间隔时间:

1
2
3
4
5
6
7
# 全局使用
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_RATES": {
"users_ip": '3/m',
"users_username": '20/m'
}
}

然后只需要重写一个方法 get_cache_key 即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
from rest_framework.throttling import SimpleRateThrottle
from rest_framework.views import APIView


# 匿名用户示例:以用户的 ip 作为标识
class VisitThrottle(SimpleRateThrottle):
# 定义 key,框架会用这个 key 去 setting 中找你设置时间间隔,比如 3/m。
# 之后会通过 parse_rate 函数解析为秒。 {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
scope = 'users_ip'

# 父类中 allow_request 函数替你实现了计算不同用户访问频率的功能,但是需要重写这个方法来返回一个 key,用于标识一个用户
# 框架利用 django 内置的缓存来保存用户的时间信息
def get_cache_key(self, request, view):
# 以用户的 ip 作为标识
# 父类实现了 get_idnet 函数,可以帮你获取用户的 ip。也可根据自己需求定义
return self.get_ident(request)

# 非匿名用户示例:以用户名作为标识
class UserThrottle(SimpleRateThrottle):
scope = 'users_username'

def get_cache_key(self, request, view):
# 以用户的用户名作为标识
return request.user.username


class AuthView(APIView):
"""
用于用户登录认证
"""
authentication_classes = []
permission_classes = []
throttle_classes = [VisitThrottle,]

def post(self, request, *args, **kwargs):

ret = {'code': 1000, 'msg': None}
try:
user = request._request.POST.get('username')
pwd = request._request.POST.get('password')
obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
if not obj:
ret['code'] = 1001
ret['msg'] = "用户名或密码错误"
# 为登录用户创建 token
token = md5(user)
# 存在就更新,不存在就创建
models.UserToken.objects.update_or_create(user=obj, defaults={'token': token})
ret['token'] = token
except Exception as e:
ret['code'] = 1002
ret['msg'] = '请求异常'

return JsonResponse(ret)

版本

源码总体流程:

1
2
3
4
5
self.dispatch
self.initial(request, *args, **kwargs)
self.determine_version(request, *args, **kwargs)
self.versioning_class,获取解析类,比如 URLPathVersioning 类,或自定义类
根据不同的类,执行类中的 determine_version 方法,最后 return version

URL 中通过 GET 传参(不推荐)

通过自定义类实现。

url: http://127.0.0.1:8000/api/users/?version=v2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ParamVersion(object):

def determine_version(self, request, *args, **kwargs):
version = request.query_params.get('version')
return version


class UsersView(APIView):

versioning_class = ParamVersion

def get(self, request, *args, **kwargs):
# 使用框架,定义指定的类 versioning_class = ParamVersion 之后,就不用下面这样自己获取了
# version = request._request.GET.get('version')
# print(version)
# version = request.query_params.get('version')
# print(version)

print(request.version)

return HttpResponse('用户列表')

在 URL 中传参(推荐使用)

使用 rest_framework 框架中的 URLPathVersioning 类实现。

1、定义配置文件:

1
2
3
4
5
6
7
REST_FRAMEWORK = {
# 告诉框架你要使用的 url 类
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
"DEFAULT_VERSION": 'v1',
"ALLOWED_VERSIONS": ['v1', 'v2'],
"VERSION_PARAM": 'version',
}

定义路由系统:

url: http://127.0.0.1:8000/api/v1/users/

1
2
3
4
5
6
7
8
9
10
11
12
# 一级路由
urlpatterns = [
# url(r'^admin/', admin.site.urls),
url(r'^api/', include('api.urls')),
]

# 二级路由
urlpatterns = [
# url(r'^admin/', admin.site.urls),
url(r'^(?P<version>[v1|v2]+)/users/$', views.UsersView.as_view(), name='uuu'),
]

在视图中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class UsersView(APIView):

def get(self,request,*args,**kwargs):

# 1. 获取版本
print(request.version)

# 2. 获取处理版本的对象
print(request.versioning_scheme)

# 3. 反向生成 URL(rest framework),自动通过 request 获取 version
u1 = request.versioning_scheme.reverse(viewname='uuu',request=request)
print(u1)

# 4. 反向生成 URL(Django 自带),需要手动指定 version
u2 = reverse(viewname='uuu',kwargs={'version':2})
print(u2)

return HttpResponse('用户列表')

解析器

由于 Django 中使用 request.POST 方法获取时,要求:

  1. 要求请求头中必须有值 Content-Type: application/x-www-form-urlencoded
  2. 数据格式要求 name=alex&age=18&gender = 男

同时满足以上两点时,request.POST 中才有值,否则只能去 request.body 中才能解析数据。

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 符合要求


// a. form 表单提交,
// POST 有

// 默认会自带请求头 Content-Type: application/x-www-form-urlencoded
// 默认的数据格式也会是 name=alex&age=18&gender = 男
<form method...>
input...
input...
input...
</form>

// b. ajax 提交
// POST 有

$.ajax({
url:...
type:POST,
// data 中的数据是字典,但是会自动转换成符合要求的格式,并带上符合要求的请求头
data:{name:alex,age=18} # 内部转化 name=alex&age=18&gender = 男
})


// 不符合要求的


// 情况一:
// POST 无;body 有值

$.ajax({
url:...
type:POST,
// 将请求头定制为这样
headers:{'Content-Type':"application/json"}
data:{name:alex,age=18} // 内部转化为 name=alex&age=18&gender = 男
})

// 情况二:
// POST 无;body 有值

// 只能 json.loads(request.body)
$.ajax({
url:...
type:POST,
headers:{'Content-Type':"application/json"}
data:JSON.stringfy({name:alex,age=18}) // 变成字符串 {name:alex,age:18...}
})

原来 Django 不支持解析的方式,使用 rest_framework 解析器,就可以对请求体数据进行解析了。

全局配置:

1
2
3
4
5
6
7
8
9
REST_FRAMEWORK = {
"DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",
"DEFAULT_VERSION":'v1',
"ALLOWED_VERSIONS":['v1','v2'],
"VERSION_PARAM":'version',

# 指定要使用的解析器
"DEFAULT_PARSER_CLASSES":['rest_framework.parsers.JSONParser','rest_framework.parsers.FormParser']
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class ParserView(APIView):
# 全局配置了,这里就不用写了
# parser_classes = [JSONParser, FormParser]
"""
JSONParser: 表示只能解析 content-type:application/json 头
FormParser: 表示只能解析 content-type:application/x-www-form-urlencoded 头
"""


def post(self, request, *args, **kwargs):
"""
允许用户发送 JSON 格式数据
a. content-type: application/json
b. {'name':'alex',age:18}
"""

"""
1. 获取用户请求
2. 获取用户请求体
3. 根据用户请求头 和 parser_classes = [JSONParser,FormParser,] 中支持的请求头进行比较
4. JSONParser 对象去请求体
5. request.data
"""
print(request.data)

return HttpResponse('ParserView')

源码流程:

1
2
3
dispatch
initialize_request
在 Request 中封装你指定的 parser_classes

序列化

常用的 serializers.fieild

CharField、BooleanField、IntegerField、DateTimeField
等等等等 email 什么的

1
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

参数:

  • read_only:True 表示不允许用户自己上传,只能由后端给前端输出。如果某个字段设置了 read_only=True,那么就不需要进行数据验证,只会在返回时,将这个字段序列化后返回。

    • 举个简单的例子:在用户进行购物的时候,用户 post 订单时,肯定会产生一个订单号,而这个订单号应该由后台逻辑完成,而不应该由用户 post 过来,如果不设置 read_only=True,那么验证的时候就会报错。
1
order_sn = serializers.CharField(readonly=True)
  • write_only: 与 read_only 对应;
  • required: 顾名思义,就是这个字段是否必填;
  • allow_null/allow_blank:是否允许为 NULL/空
  • error_messages: 出错时,信息提示。
1
2
name = serializers.CharField(required=True, min_length=6,
error_messages={ 'min_length': '名字不能小于6个字符', 'required': '请填写名字'})
  • label: 字段显示设置,如 label='验证码'
  • help_text: 在指定字段增加一些提示文字,这两个字段作用于 api 页面比较有用
  • style: 说明字段的类型,这样看可能比较抽象,看下面例子:
1
2
3
4
5
6
7
# 在 api 页面,输入密码就会以 * 显示
password = serializers.CharField(
style={'input_type': 'password'})
# 会显示选项框
color_channel = serializers.ChoiceField(
choices=['red', 'green', 'blue'],
style={'base_template': 'radio.html'})

将数据库的值序列化

通过自定义一个类,将从数据库查到的 QuerySet 类型的对象,自动序列化。

入门使用,快速理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from rest_framework import serializers


# 自定义类,在这里指定想显示什么字段
# 注意:继承 serializers.Serializer
class RolesSerializer(serializers.Serializer):
id = serializers.IntegerField()
title = serializers.CharField()


class RolesView(APIView):
def get(self, request, *args, **kwargs):

# 方式一:
roles = models.Role.objects.all().values('id', 'title') # 需要在这里指定要显示的字段
roles = list(roles)
ret = json.dumps(roles, ensure_ascii=False)

# 方式二:
# 对于 [obj,obj,obj,] 的情况
roles = models.Role.objects.all()
# 将数据集传递到自定义类中即可
ser = RolesSerializer(instance=roles, many=True) # many 表示是数据集
ret = json.dumps(ser.data, ensure_ascii=False) # ser.data 已经是转换完成的结果

# 对于只有单个 obj 的情况
role = models.Role.objects.all().first()
# 将数据集传递到自定义类中即可
ser = RolesSerializer(instance=role, many=False) # many 为 False 代表只有一个数据
ret = json.dumps(ser.data, ensure_ascii=False)

return HttpResponse(ret)

对于 外键返回字段的 “人类可读” 值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class UserGroup(models.Model):
title = models.CharField(max_length=32)


class UserInfo(models.Model):
user_type_choices = (
(1, '普通用户'),
(2, 'VIP'),
(3, 'SVIP'),
)
user_type = models.IntegerField(choices=user_type_choices) # “人类可读” 字段

username = models.CharField(max_length=32, unique=True)
password = models.CharField(max_length=64)

group = models.ForeignKey("UserGroup") # 外键
roles = models.ManyToManyField("Role")



class UserInfoSerializer(serializers.Serializer):
# 获取的只是 id 值
user_type_1 = serializers.CharField(source="user_type") # row.user_type
# 这样可以获取 “人类可读” 字段
user_type_2 = serializers.CharField(source="get_user_type_display") # row.get_user_type_display()。 这里不需要加 () 了,因为会自动判断,如果是可调用的则自动加上 ()

username = serializers.CharField()
password = serializers.CharField()

# 这样可以显示外键,但通过 source 是做不到很细的显示粒度
gp = serializers.CharField(source="group.title")
# rls = serializers.CharField(source="roles.all")

# 通过自定义一个函数,可以定制要显示的内容
rls = serializers.SerializerMethodField()
# 函数名要求以 get 开头,字段名结尾
def get_rls(self, row):

role_obj_list = row.roles.all()

ret = []
for item in role_obj_list:
ret.append({'id': item.id, 'title': item.title})
return ret

使用 ModelSerializer 自动生成字段

之前的方法,需要我们逐个配置每个字段,而继承自 serializers.ModelSerializer 的话,会自动帮我们将整张表序列化。

它为创建一个序列化,提供了一个快捷的方式,它会自动创建一个 Serializer 类,字段与 model 中的字段一一对应。

ModelSerializer 类与常规 Serializer 类相同,不同之处在于:

  • 它会根据模型自动生成一组字段。
  • 它会自动为序列化类生成验证器,例如 unique_together 验证器。
  • 它包含 .create().update() 的简单默认实现。它能够满足将 post 或 patch 上来的数据进行进行直接地创建与更新。如果有额外需求,那么就可以重载 create 与 update 方法。

声明 ModelSerializer 时,在 Meta 中设置 fields 字段,系统会自动进行映射成序列化字段,省去每个字段再写一个 field

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class UserInfoSerializer(serializers.ModelSerializer):
oooo = serializers.CharField(source="get_user_type_display")
rls = serializers.SerializerMethodField()

class Meta:
model = models.UserInfo

# 表示所有字段,这样会将整张表都序列化,不过只能显示简单的格式,不能返回字段的 “人类可读” 值之类的操作
fields = "__all__"

# 混合灵活使用:如需要自定义显示,使用 fields 配置即可。未自定义的会直接简单显示,自定义过的则会按照定义的显示
fields = ['id', 'username', 'password', 'oooo', 'rls', 'group']

# 除去指定的某些字段
exclude = ('rls',)

# 只读字段,当多个字段都为只读,我们可以这样设置,不用每个字段都设置 read_only=True
read_only_fields =(’field_name‘,) # editable=False、AutoField 默认只读,不用添加



def get_rls(self, row):
role_obj_list = row.roles.all()

ret = []
for item in role_obj_list:
ret.append({'id': item.id, 'title': item.title})
return ret

终极全自动化方案:自动序列化连表(注意效率问题)

1
2
3
4
5
6
7
8
class UserInfoSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
# fields = "__all__"
fields = ['id', 'username', 'password', 'group', 'roles']

# depth 代表深度,为 0 则只返回本表数值,为 1、2... 时会自动关联指定层数的表
depth = 1 # 官方建议 0 ~ 10,老师建议最多 3 ~ 4

ModelSerializer 主要需要解决的 2 个问题(2 个应用方案):
1、某个字段不属于 Model,它是 write_only,需要用户传进来,但我们不能对它进行 save(),因为 ModelSerializer 是基于 Model 的,这个字段在 Model 中没有对应。这个时候,我们可以通过重载 validate 方法来解决。比如:在用户注册时,我们需要填写验证码,这个验证码只需要验证,不需要保存到用户这个 Model 中。

1
2
3
def validate(self, attrs):
del attrs["code"]
return attrs

2、某个字段不属于指定 Model,它是 read_only,只需要将它序列化传递给用户,但是在这个 Model 中,没有这个字段!这时就可以使用 SerializerMethodField。假设需要返回用户加入这个网站多久了,不可能维持这样加入的天数这样一个数据,一般会记录用户加入的时间点,然后当用户获取这个数据,我们再计算返回给它。

1
2
3
4
5
6
7
8
9
10
class UserSerializer(serializers.ModelSerializer):    
days_since_joined = serializers.SerializerMethodField()

class Meta:
model = User

# 方法写法:get_ + 字段
def get_days_since_joined(self, obj):
# obj 指这个 model 的对象
return (now() - obj.date_joined).days

生成链接

通过反向解析向前台返回一个链接。

示例:当用户请求 Userinfo 数据时,将其中的 group 字段返回 url,而不是真正的值。这个 url 可以自动生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
urlpatterns = [
url(r'^(?P<version>[v1|v2]+)/userinfo/$', views.UserInfoView.as_view()),
url(r'^(?P<version>[v1|v2]+)/group/(?P<xxx>\d+)$', views.GroupView.as_view(), name='gp'),
]


class UserInfoSerializer(serializers.ModelSerializer):
# 传三个参数
# view_name='gp' 路由名字,用来反向解析
# lookup_field='group_id' 要反向解析的参数值
# lookup_url_kwarg='xxx' 有名分组的名字
group = serializers.HyperlinkedIdentityField(view_name='gp', lookup_field='group_id', lookup_url_kwarg='xxx')

class Meta:
model = models.UserInfo
# fields = "__all__"
fields = ['id', 'username', 'password', 'group', 'roles']
depth = 0 # 0 ~ 10


class UserInfoView(APIView):
def get(self, request, *args, **kwargs):
users = models.UserInfo.objects.all()
# context={'request':request} 是必写的
ser = UserInfoSerializer(instance=users, many=True, context={'request': request})
ret = json.dumps(ser.data, ensure_ascii=False)
return HttpResponse(ret)

# url":"http://127.0.0.1:8000/group/1"
# context={'request':request}:得到了域名 http://127.0.0.1:8000
# view_name:得到了 group/(?P<xxx>\d+)
# lookup_field 和 lookup_url_kwarg:得到了 1
# 把这三个拼接起来就成了一条路由 http://127.0.0.1:8000/group/1

源码:

  • __new__ 时,通过 if kwargs.pop('many', False): 判断 many 的值。
    • 当为 False 时是单一对象,交给 Serializer 类处理;
    • 当为 True 时是 QuerySet,交给 ListSerializer 类去处理;
  • ser.data:已经是转换完成的结果。

请求数据校验

下面介绍和校验相关的一些使用方法。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14

from rest_framework import serializers

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

erializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'})
serializer.is_valid()

# 如 False,获取错误
serializer.errors # {'email': [u'Enter a valid e-mail address.'], 'created': [u'This field is required.']}

2. 抛出无效数据的异常

.is_valid() 方法有一个可选的参数 raise_exception,当存在错误时将会抛出一个 serializers.ValidationError 异常。这些异常由 REST framework 提供的默认异常处理程序自动处理,默认情况下将返回 HTTP 400 Bad Request 响应。

3. 字段级别自定义的验证
你可以向你的 Serializer 子类中添加一个方法,这个方法可以为指定的字段进行自定义的验证。方法名字的格式为 .validate_<field_name>,其中 <field_name> 就是需要验证的字段值。

这个验证方法可以获得一个参数,就是用户传的值。

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

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
from rest_framework import serializers

class BlogPostSerializer(serializers.Serializer):
title = serializers.CharField(max_length=100)
content = serializers.CharField()

def validate_title(self, value):
"""
Check that the blog post is about Django.
"""
if 'django' not in value.lower():
raise serializers.ValidationError("Blog post is not about Django")
return value

4. 对象级别的验证
上一个方法是对指定字段进行验证,如果需要对多个字段(也就是整个对象)进行验证,添加一个名为 validate() 的方法即可。

这个方法可以获得一个参数,为字典类型。

最后依然需要应该抛出一个 ValidationError 异常,或者返回经过验证的值。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from rest_framework import serializers

class EventSerializer(serializers.Serializer):
description = serializers.CharField(max_length=100)
start = serializers.DateTimeField()
finish = serializers.DateTimeField()

def validate(self, data):
"""
Check that the start is before the stop.
"""
if data['start'] > data['finish']:
raise serializers.ValidationError("finish must occur after start")
return data

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

validators 可以直接作用于某个字段,这个时候,它与单独的 validate 作用差不多。

例如:

1
2
3
4
5
6
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])

6. 自定义错误信息

1
2
3
4
5
6
class BooksSerializers(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = '__all__'
# 类似forms组件
name = serializers.CharField(max_length=10, min_length=3, error_messages={'max_length': '最长为10','min_length': '最短为3','required':'不能为空'})

综合示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 自定义校验规则,在 call 中判断
class XXValidator(object):
def __init__(self, base):
self.base = base

def __call__(self, value):
if not value.startswith(self.base):
message = '标题必须以 %s 为开头。' % self.base
raise serializers.ValidationError(message)

def set_context(self, serializer_field):
"""
This hook is called by the serializer instance,
prior to the validation call being made.
"""
# 执行验证之前调用,serializer_fields 是当前字段对象
pass


# 这样只校验了值是否为空
class UserGroupSerializer(serializers.Serializer):
title = serializers.CharField(error_messages={'required': '标题不能为空'})
# 这样会根据自定义规则校验,“标题必须以老男人开头”
class UserGroupSerializer(serializers.Serializer):
title = serializers.CharField(error_messages={'required': '标题不能为空'}, validators=[XXValidator('老男人'), ])

# 使用钩子函数,value 是用户传的值
def validate_title(self, value):
# 如果有问题 raise 错误即可
from rest_framework import exceptions
raise exceptions.ValidationError('看你不顺眼')
# 没问题就返回
return value


class UserGroupView(APIView):

def post(self, request, *args, **kwargs):

# 反序列化把 request.data 中的 JSON 格式的数据传入 data 中
ser = UserGroupSerializer(data=request.data)

# is_valid 对反序列化后的数据进行校验
if ser.is_valid(): # 校验成功
print(ser.validated_data['title'])
else:
print(ser.errors)

return HttpResponse('提交数据')

保存实例

如果我们希望能够返回基于验证数据的完整对象实例,我们需要实现其中一个或全部实现 .create()update() 方法。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CommentSerializer(serializers.Serializer):
email = serializers.EmailField()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()

def create(self, validated_data):
return Comment.objects.create(**validated_data) # model 对象

def update(self, instance, validated_data):
instance.email = validated_data.get('email', instance.email)
instance.content = validated_data.get('content', instance.content)
instance.created = validated_data.get('created', instance.created)
# instance.save() model 对象
return instance

ps.create().update() 方法都是可选的。你可以根据你序列化器类的用例不实现、实现它们之一或都实现。

现在当我们反序列化数据的时候,基于验证过的数据我们可以调用 .save() 方法返回一个对象实例。

1
comment = serializer.save()

调用 .save() 方法将创建新实例或者更新现有实例,具体取决于实例化序列化器类的时候是否传递了现有实例:

1
2
3
4
5
# .save() will create a new instance.
serializer = CommentSerializer(data=data)

# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)

默认情况下,序列化程序必须为所有必填字段传递值,否则会引发验证错误。你可以使用 partial 参数以允许部分更新

1
2
# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data,partial=True)

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class BooksSerializers(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = '__all__'


# 添加数据
def post(self, request):
response = {'status': 200, 'msg': '添加成功', 'data': None}
ret = BooksSerializers(data=request.data)
# is_valid 对反序列化后的数据进行校验
if ret.is_valid():
# 对校验成功的数据保存
ret.save()
response['data'] = ret.data
else:
response['status'] = 201
# errors:错误信息
response['data'] = ret.errors
response['msg'] = '添加失败'
return JsonResponse(response, safe=False)


# 如果是更新数据
def put(self, request, id):
response = {'status': 200, 'msg': '修改成功', 'data': None}
books = models.Book.objects.filter(pk=id).first()
if books:
# 不传instance,调save(),往数据库新增数据
# 传instance,调save(),修改数据
# BooksSerializers(data=request.data, instance='要更新的对象')
ret = BooksSerializers(data=request.data, instance=books)
if ret.is_valid():
ret.save()
response['data'] = ret.data
else:
response['status'] = 201
response['data'] = ret.errors
response['msg'] = '修改失败'
else:
response['status'] = 201
response['msg'] = '修改对象不存在'
return JsonResponse(response, safe=False)

分页

基本分页

看第 n 页,每页显示 n 条数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from rest_framework.pagination import PageNumberPagination


class MyPageNumberPagination(PageNumberPagination):
page_size = 2 # 默认值,默认是 None 不启用分页
page_size_query_param = 'size' # 指定 size 的关键字
max_page_size = 5 # 每页最大 size

page_query_param = 'page' # 指定 page 的关键字


class Pager1View(APIView):

def get(self, request, *args, **kwargs):
# 获取所有数据
roles = models.Role.objects.all()

# 创建分页对象
pg = PageNumberPagination() # 默认的不能调整 size 大小
pg = MyPageNumberPagination() # 自定义可以调整 size 大小

# 在数据库中获取分页的数据
pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self)

# 对数据进行序列化
ser = PagerSerialiser(instance=pager_roles, many=True)

return Response(ser.data)

# 这个方法可以自动在响应报文中,添入上一页、下一页的 url
return pg.get_paginated_response(ser.data)

偏移、Limit

在 n 个位置,向后查看 n 条数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from api.utils.serializsers.pager import PagerSerialiser
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination


class MyLimitOffsetPagination(LimitOffsetPagination):
default_limit = 2 # 默认
limit_query_param = 'limit'
offset_query_param = 'offset'
max_limit = 5 # 最大


class Pager1View(APIView):

def get(self, request, *args, **kwargs):
# 获取所有数据
roles = models.Role.objects.all()

# 创建分页对象
pg = LimitOffsetPagination()
pg = MyLimitOffsetPagination() # 使用自定义可以配置最大、最小值

# 在数据库中获取分页的数据
pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self)

# 对数据进行序列化
ser = PagerSerialiser(instance=pager_roles, many=True)

return Response(ser.data)
# return pg.get_paginated_response(ser.data)

加密分页

返回值内会有上一页和下一页的链接,值是加密的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination


class MyCursorPagination(CursorPagination):
cursor_query_param = 'cursor'
page_size = 2
ordering = 'id'
page_size_query_param = None
max_page_size = None


class Pager1View(APIView):

def get(self, request, *args, **kwargs):
# 获取所有数据
roles = models.Role.objects.all()

# 创建分页对象
# pg = CursorPagination()
pg = MyCursorPagination()

# 在数据库中获取分页的数据
pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self)

# 对数据进行序列化
ser = PagerSerialiser(instance=pager_roles, many=True)

# return Response(ser.data)
return pg.get_paginated_response(ser.data)

视图

View 的继承关系:

  • View:Django 自带 view;
  • APIView:REST-framework 基本 view;
  • GenericAPIView: 封装了一些参数,可以少写一些参数,用途不大;
  • GenericViewSet: 在 url 上可以将请求方法自定义映射;
    • 比如 url(r'/', views.View1View.as_view({'get': 'list','post':'create'}))
    • get 请求本来应该执行 get 方法,现在会执行 list 方法
  • ModelViewSet: 实现了增删改、查多、查一
    • 继承了:mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet,

ModelViewSet 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 面向列表,只需要查、增
url(r'^(?P<version>[v1|v2]+)/v1/$',
views.View1View.as_view({'get': 'list', 'post': 'create'})),
# 面向单个对象 id,有增删改查
url(r'^(?P<version>[v1|v2]+)/v1/(?P<pk>\d+)/$',
views.View1View.as_view({'get': 'retrieve', 'delete': 'destroy', 'put': 'update', 'patch': 'partial_update'})),

# 视图:
from rest_framework.viewsets import GenericViewSet, ModelViewSet

class MySerialiser(serializers.ModelSerializer):
class Meta:
model = models.Role
fields = "__all__"

# 增删改查都有
class View1View(ModelViewSet):
queryset = models.Role.objects.all()
serializer_class = MySerialiser
pagination_class = PageNumberPagination


# 只有增
# class View1View(CreateModelMixin, GenericViewSet):

路由

REST-framework 的渲染界面原生支持在 url 末尾加 /api/v1/user/1/?format=json,使返回 json 格式的数据。

可以配置 url ,实现访问 /api/v1/user/1.json 达到同样的效果。

1
2
3
4
5
6
7
# http://127.0.0.1:8000/api/v1/v1/?format=json
url(r'^(?P<version>[v1|v2]+)/v1/$', views.View1View.as_view({'get': 'list','post':'create'})),
# http://127.0.0.1:8000/api/v1/v1.json
url(r'^(?P<version>[v1|v2]+)/v1\.(?P<format>\w+)$', views.View1View.as_view({'get': 'list','post':'create'})),

url(r'^(?P<version>[v1|v2]+)/v1/(?P<pk>\d+)/$', views.View1View.as_view({'get': 'retrieve','delete':'destroy','put':'update','patch':'partial_update'})),
url(r'^(?P<version>[v1|v2]+)/v1/(?P<pk>\d+)\.(?P<format>\w+)$', views.View1View.as_view({'get': 'retrieve','delete':'destroy','put':'update','patch':'partial_update'})),

以上 url 可以通过路由注册功能,自动生成

注意:这样自动生成的方式,增删改查功能都有。

1
2
3
4
5
6
7
8
9
10
11
from api import views
from rest_framework import routers


router = routers.DefaultRouter()
router.register(r'xxxxx', views.View1View)
router.register(r'rt', views.View1View)

urlpatterns = [
url(r'^(?P<version>[v1|v2]+)/', include(router.urls)),
]

渲染

使用 from rest_framework.response import Response 返回,框架会自动渲染出 web 页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from rest_framework.response import Response
from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer

class TestView(APIView):
# 局部指定
renderer_classes = [JSONRenderer,BrowsableAPIRenderer]

def get(self, request, *args, **kwargs):
return Response(data)


REST_FRAMEWORK = {
"DEFAULT_RENDERER_CLASSES":[
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer'
]
}

其实就算没有上边的配置,只要使用 rest_framework.response.Response 返回数据即可。

框架渲染时会从自己的 templates 目录中读取模板文件,如果要自定义 logo 更改这里边的文件即可。