什么是RESTful API以及Django RestFramework

一. 什么是RESTful API以及Django RestFramework

1. 协议

API与用户的通信协议,总是使用HTTPS协议。

2. 域名

3. 版本

应该将API的版本号放入URL: https://api.example.com/v1/
另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。GitHub Developer Guide采用这种做法,跨域时会引发多次请求

4. 路径(Endpoint)

路径又称”终点”(endpoint),表示API的具体网址。
在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的”集合”(collection),所以API中的名词也应该使用复数。
举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

1
2
3
https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees

5. HTTP动词

对于资源的具体操作类型,由HTTP动词表示。
常用的HTTP动词有下面五个(括号里是对应的SQL命令)

1
2
3
4
5
GET(SELECT):从服务器取出资源(一项或多项)
POST(CREATE):在服务器新建一个资源
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)
DELETE(DELETE):从服务器删除资源

还有两个不常用的HTTP动词

1
2
HEAD:获取资源的元数据
OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的

下面是一些栗子:

1
2
3
4
5
6
7
8
GET /zoos:列出所有动物园
POST /zoos:新建一个动物园
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

6. 过滤信息(Filtering)

如果记录数量很多,服务器不可能都将它们返回给用户.API应该提供参数,过滤返回结果
常见的参数形式如下:

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

参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。

7. 状态码(Status Codes)

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)

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

注意: 状态码的完全列表参见这里

8. 错误处理(Error handling)

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

1
2
3
{
error: "Invalid API key"
}

9. 返回结果

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

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

10. Hypermedia API

RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档:

1
2
3
4
5
6
{"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos",
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}}

上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。
Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。

1
2
3
4
5
{
"current_user_url": "https://api.github.com/user",
"authorizations_url": "https://api.github.com/authorizations",
// ...
}

从上面可以看到,如果想获取当前用户的信息,应该去访问\api.github.com/user,然后就得到了下面结果:

1
2
3
4
{
"message": "Requires authentication",
"documentation_url": "https://developer.github.com/v3"
}

上面代码表示,服务器给出了提示信息,以及文档的网址

11. 其他

  • API的身份认证应该使用OAuth 2.0框架。
  • 服务器返回的数据格式,应该尽量使用JSON,避免使用XML。

RESTful API 设计指南 - 阮一峰的网络日志
GitHub - aisuhua/restful-api-design-references: RESTful API 设计参考文献列表

二. 基于Django的实现

2.1 路由系统

1
2
3
4
from app01 import views
urlpatterns = [
url(r'^users', views.UserView.as_view())
]

2.2 CBV试图

1
2
3
4
5
6
7
8
class UserView(View):
def get(self, request, *args, **kwargs):
result = {"status": True, "data": "This is a get request", "msg": None, "code": 2000}
return JsonResponse(result, status=200)

def post(self, request, *args, **kwargs):
result = {"status": True, "data": "This is a post request", "msg": None, "code": 2000}
return JsonResponse(result, status=200)

三. 基于Django Rest Framework 框架实现

3.1 基本流程

路由:URL.py

1
2
3
4
5
6
from django.conf.urls import url
from app01 import views

urlpatterns = [
url(r'^index', views.IndexView.as_view())
]

试图: views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from rest_framework.views import APIView
from rest_framework.response import Response

class IndexView(APIView):
"""
请求到来之后,都要执行dispatch方法,dispatch方法根据请求方式不同触发反射 get/post/put 等方法
注意:APIView中的dispatch方法有好多好多的功能
"""

def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

def get(self, request, *args, **kwargs):
return Response('GET请求,响应内容')

def post(self, request, *args, **kwargs):
return Response('POST请求,响应内容')

def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容')

⚠️ : 以上是Django rest framework框架基本流程,重要的功能是在APIView的dispatch中触发,也是框架的源码入口

3.2 认证和授权

3.2.1 基于token的验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
urls.py

from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include

urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/(?P<version>\w+)/', include('app01.urls')),
]

$ cat app01/urls.py
from django.conf.urls import url
from app01 import views

urlpatterns = [
url(r'^auth/$', views.AuthView.as_view()),
url(r'^user/$', views.UserView.as_view()),
]
1
2
3
4
5
6
7
8
9
10
11
$ cat models.py 
from django.db import models

class UserInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)


class UserToken(models.Model):
user = models.OneToOneField('UserInfo', on_delete=True)
token = models.CharField(max_length=64)
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
$ cat views.py
from django.shortcuts import render, HttpResponse

# Create your views here.
from django.http import JsonResponse
from rest_framework.views import APIView
from app01 import models
import uuid

class AuthView(APIView):
authentication_classes = [] # 登录认证接口覆盖默认的toekn认证类

def post(self, request, *args, **kwargs):
response = {'code': 1000}
user = request.data.get('username')
pwd = request.data.get('password')

obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
if not obj:
response['code'] = 1001
response['msg'] = '用户或密码错误'
return JsonResponse(response, json_dumps_params={'ensure_ascii': False})

try:
token = str(uuid.uuid4())
models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})
response['token'] = token
except Exception as e:
print("Error: ", e)
return JsonResponse(response, json_dumps_params={'ensure_ascii': False})


class UserView(APIView):
def get(self, request, *args, **kwargs):
print(request.user)
print(request.auth)
return HttpResponse('user.get: %s' % request.user)

def post(self, request, *args, **kwargs):
return HttpResponse('user.post')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ cat app01/utils/auth.py

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from app01 import models

class TokenAuthtication(BaseAuthentication):
def authenticate(self, request):
"""
:param request:
:return:
(user,auth) 表示认证成功,并将元组分别赋值给request.user/request.auth
:raise AuthenticationFailed('认证失败') 表示认证失败
"""

token = request.query_params.get('token')
if not token:
raise AuthenticationFailed("用户Token未携带")

token_obj = models.UserToken.objects.filter(token=token).first()
if not token_obj:
raise AuthenticationFailed("Token已失效或错误")
return (token_obj.user.username, token_obj)
1
2
3
4
5
$ cat settings.py

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.auth.TokenAuthtication', ],
}

验证:

  1. 首先在数据库中构造请求登录的用户名密码
  2. 构造数据获取token信息
    1
    2
    3
    4
    5
    6
    7
    8
    $ curl -X POST \
    > http://127.0.0.1:8001/api/v1/auth/ \
    > -H 'Cache-Control: no-cache' \
    > -H 'Content-Type: application/json' \
    > -H 'Postman-Token: 8439ce0a-94ff-3970-c8c2-4c6bc200bb4f' \
    > -d '{"username": "eric","password":"123"}'
    返回:
    {"code": 1000, "token": "4c90f696-008b-4e30-86fa-4b00b6fc9237"}
  3. 携带token访问user接口,获取用户信息
    1
    2
    3
    4
    5
    >   'http://127.0.0.1:8001/api/v1/user/?token=4c90f696-008b-4e30-86fa-4b00b6fc9237' \
    > -H 'Cache-Control: no-cache' \
    > -H 'Postman-Token: 1b4125ad-7ad1-1cca-70fd-6a00cca16d96'
    返回:
    user.get: eric

3.2.2 基于请求头认证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ cat urls.py

from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include

urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/(?P<version>\w+)/', include('app01.urls')),
]

$ cat app01/urls.py
from django.conf.urls import url
from app01 import views

urlpatterns = [
url(r'^auth/$', views.AuthView.as_view()),
url(r'^user/$', views.UserView.as_view()),
]
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
55
56
57
58
59
60
61
$ cat app01/utils/auth.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "shuke"
# Date: 2018/6/1


from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework import exceptions
from app01 import models

class HeaderAuthentication(BaseAuthentication):
def authenticate(self, request):
"""
用户认证,如果验证成功后返回元组: (用户,用户Token)
:param request:
:return:
None,表示跳过该验证;
如果跳过了所有认证,默认用户和Token和使用配置文件进行设置
self._authenticator = None
if api_settings.UNAUTHENTICATED_USER:
self.user = api_settings.UNAUTHENTICATED_USER()
else:
self.user = None

if api_settings.UNAUTHENTICATED_TOKEN:
self.auth = api_settings.UNAUTHENTICATED_TOKEN()
else:
self.auth = None
(user,token)表示验证通过并设置用户名和Token;
AuthenticationFailed异常
"""
import base64
import uuid
auth = request.META.get('HTTP_AUTHORIZATION', b'')
if auth:
auth = auth.encode('utf-8')
auth = auth.split()
if not auth or auth[0].lower() != b'basic':
raise exceptions.AuthenticationFailed('验证失败')
if len(auth) != 2:
raise exceptions.AuthenticationFailed('验证失败')
username, part, password = base64.b64decode(auth[1]).decode('utf-8').partition(':')
try:
obj = models.UserInfo.objects.filter(username=username, password=password).first()
if not obj:
raise exceptions.AuthenticationFailed('用户名或密码错误')
token = str(uuid.uuid4())
token_obj, status = models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})
return (token_obj.user.username, token_obj)
except Exception as e:
print("Error: ", e)

def authenticate_header(self, request):
"""
Return a string to be used as the value of the `WWW-Authenticate`
header in a `401 Unauthenticated` response, or `None` if the
authentication scheme should return `403 Permission Denied` responses.
"""
return 'Basic realm=api'
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
$ cat views.py
from django.shortcuts import render, HttpResponse

# Create your views here.
from django.http import JsonResponse
from rest_framework.views import APIView
from app01.utils.auth import HeaderAuthentication
from app01 import models
import uuid


class AuthView(APIView):
authentication_classes = []

def post(self, request, *args, **kwargs):
response = {'code': 1000}
user = request.data.get('username')
pwd = request.data.get('password')

obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
if not obj:
response['code'] = 1001
response['msg'] = '用户或密码错误'
return JsonResponse(response, json_dumps_params={'ensure_ascii': False})

try:
token = str(uuid.uuid4())
models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})
response['token'] = token
except Exception as e:
print("Error: ", e)
return JsonResponse(response, json_dumps_params={'ensure_ascii': False})


class UserView(APIView):
authentication_classes = [HeaderAuthentication, ]

def get(self, request, *args, **kwargs):
print(request.user)
print(request.auth)
return HttpResponse('user.get: %s,token: %s' % (request.user, request.auth))

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

验证:

  1. 首先在数据库中构造请求登录的用户名密码
  2. 构造数据获取token信息,建议使用postman进行构造测试
    1
    2
    3
    4
    5
    6
    7
    $ curl -X GET \
    http://127.0.0.1:8001/api/v1/user/ \
    -H 'Authorization: Basic c2h1a2U6MTIz' \
    -H 'Cache-Control: no-cache' \
    -H 'Postman-Token: ba3f99ce-da6a-7e5f-3e0b-3c79129ecca1'
    返回:
    user.get: shuke,token: 2d3af34a-1598-4d5a-af56-7f29a706a26e

    3.2.3 多个认证规则

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    $ cat urls.py

    from django.contrib import admin
    from django.urls import path
    from django.conf.urls import url, include

    urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api/(?P<version>\w+)/', include('app01.urls')),
    ]

    $ cat app01/urls.py
    from django.conf.urls import url
    from app01 import views

    urlpatterns = [
    url(r'^auth/$', views.AuthView.as_view()),
    url(r'^user/$', views.UserView.as_view()),
    ]
    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
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    $ cat app01/utils/auth.py
    #! /usr/bin/env python
    # -*- coding: utf-8 -*-
    # __author__ = "shuke"
    # Date: 2018/6/1


    from rest_framework.authentication import BaseAuthentication
    from rest_framework.exceptions import AuthenticationFailed
    from rest_framework import exceptions
    from app01 import models


    class TokenAuthtication(BaseAuthentication):
    def authenticate(self, request):
    """

    :param request:
    :return:
    (user,auth) 表示认证成功,并将元组分别赋值给request.user/request.auth
    :raise AuthenticationFailed('认证失败') 表示认证失败
    """

    token = request.query_params.get('token')
    if not token:
    raise AuthenticationFailed("用户Token未携带")

    token_obj = models.UserToken.objects.filter(token=token).first()
    if not token_obj:
    raise AuthenticationFailed("Token已失效或错误")
    return (token_obj.user.username, token_obj)


    class HeaderAuthentication(BaseAuthentication):
    def authenticate(self, request):
    """
    用户认证,如果验证成功后返回元组: (用户,用户Token)
    :param request:
    :return:
    None,表示跳过该验证;
    如果跳过了所有认证,默认用户和Token和使用配置文件进行设置
    self._authenticator = None
    if api_settings.UNAUTHENTICATED_USER:
    self.user = api_settings.UNAUTHENTICATED_USER()
    else:
    self.user = None

    if api_settings.UNAUTHENTICATED_TOKEN:
    self.auth = api_settings.UNAUTHENTICATED_TOKEN()
    else:
    self.auth = None
    (user,token)表示验证通过并设置用户名和Token;
    AuthenticationFailed异常
    """
    import base64
    import uuid
    auth = request.META.get('HTTP_AUTHORIZATION', b'')
    if auth:
    auth = auth.encode('utf-8')
    auth = auth.split()
    if not auth or auth[0].lower() != b'basic':
    raise exceptions.AuthenticationFailed('验证失败')
    if len(auth) != 2:
    raise exceptions.AuthenticationFailed('验证失败')
    username, part, password = base64.b64decode(auth[1]).decode('utf-8').partition(':')
    try:
    obj = models.UserInfo.objects.filter(username=username, password=password).first()
    if not obj:
    raise exceptions.AuthenticationFailed('用户名或密码错误')
    token = str(uuid.uuid4())
    token_obj, status = models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})
    return (token_obj.user.username, token_obj)
    except Exception as e:
    print("Error: ", e)

    def authenticate_header(self, request):
    """
    Return a string to be used as the value of the `WWW-Authenticate`
    header in a `401 Unauthenticated` response, or `None` if the
    authentication scheme should return `403 Permission Denied` responses.
    """
    return 'Basic realm=api'
    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
    $ cat views.py
    from django.shortcuts import render, HttpResponse

    # Create your views here.
    from django.http import JsonResponse
    from rest_framework.views import APIView
    from app01.utils.auth import HeaderAuthentication, TokenAuthtication
    from app01 import models
    import uuid


    class AuthView(APIView):
    authentication_classes = []

    def post(self, request, *args, **kwargs):
    response = {'code': 1000}
    user = request.data.get('username')
    pwd = request.data.get('password')

    obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
    if not obj:
    response['code'] = 1001
    response['msg'] = '用户或密码错误'
    return JsonResponse(response, json_dumps_params={'ensure_ascii': False})

    try:
    token = str(uuid.uuid4())
    models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})
    response['token'] = token
    except Exception as e:
    print("Error: ", e)
    return JsonResponse(response, json_dumps_params={'ensure_ascii': False})


    class UserView(APIView):
    authentication_classes = [HeaderAuthentication, TokenAuthtication]

    def get(self, request, *args, **kwargs):
    print(request.user)
    print(request.auth)
    return HttpResponse('user.get: %s,token: %s' % (request.user, request.auth))

    def post(self, request, *args, **kwargs):
    return HttpResponse('user.post')
    验证:
  3. 首先在数据库中构造请求登录的用户名密码
  4. 构造数据获取token信息,建议使用postman进行构造测试
    1
    2
    3
    4
    5
    6
    7
    $ curl -X GET \
    http://127.0.0.1:8001/api/v1/user/ \
    -H 'Authorization: Basic c2h1a2U6MTIz' \
    -H 'Cache-Control: no-cache' \
    -H 'Postman-Token: f91805b7-c742-b713-0010-c75cdbfbb24a'
    返回:
    user.get: shuke,token: 9c3ca96a-ac03-4207-aaec-20433bae6058

3.2.4 认证和权限

路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ cat urls.py

from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include

urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/(?P<version>\w+)/', include('app01.urls')),
]

$ cat app01/urls.py
from django.conf.urls import url
from app01 import views

urlpatterns = [
url(r'^auth/$', views.AuthView.as_view()),
url(r'^user/$', views.UserView.as_view()),
]

model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from django.db import models

# Create your models here.
from django.db import models


class UserInfo(models.Model):
user_type_choices = (
(1, '普通用户'),
(2, '管理员'),
(3, '超级管理员'),
)
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
user_type = models.IntegerField(choices=user_type_choices, default=1)


class UserToken(models.Model):
user = models.OneToOneField('UserInfo', on_delete=True)
token = models.CharField(max_length=64)

认证

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
$ cat app01/utils/auth.py

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "shuke"
# Date: 2018/6/1


from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework import exceptions
from app01 import models


class TokenAuthtication(BaseAuthentication):
def authenticate(self, request):
"""

:param request:
:return:
(user,auth) 表示认证成功,并将元组分别赋值给request.user/request.auth
:raise AuthenticationFailed('认证失败') 表示认证失败
"""

token = request.query_params.get('token')
if not token:
raise AuthenticationFailed("用户Token未携带")

token_obj = models.UserToken.objects.filter(token=token).first()
if not token_obj:
raise AuthenticationFailed("Token已失效或错误")
return (token_obj.user.username, token_obj)


class HeaderAuthentication(BaseAuthentication):
def authenticate(self, request):
"""
用户认证,如果验证成功后返回元组: (用户,用户Token)
:param request:
:return:
None,表示跳过该验证;
如果跳过了所有认证,默认用户和Token和使用配置文件进行设置
self._authenticator = None
if api_settings.UNAUTHENTICATED_USER:
self.user = api_settings.UNAUTHENTICATED_USER()
else:
self.user = None

if api_settings.UNAUTHENTICATED_TOKEN:
self.auth = api_settings.UNAUTHENTICATED_TOKEN()
else:
self.auth = None
(user,token)表示验证通过并设置用户名和Token;
AuthenticationFailed异常
"""
import base64
import uuid
auth = request.META.get('HTTP_AUTHORIZATION', b'')
if auth:
auth = auth.encode('utf-8')
auth = auth.split()
if not auth or auth[0].lower() != b'basic':
raise exceptions.AuthenticationFailed('验证失败')
if len(auth) != 2:
raise exceptions.AuthenticationFailed('验证失败')
username, part, password = base64.b64decode(auth[1]).decode('utf-8').partition(':')
try:
obj = models.UserInfo.objects.filter(username=username, password=password).first()
if not obj:
raise exceptions.AuthenticationFailed('用户名或密码错误')
token = str(uuid.uuid4())
token_obj, status = models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})
return (token_obj.user.username, token_obj)
except Exception as e:
print("Error: ", e)
raise exceptions.AuthenticationFailed('验证失败')

def authenticate_header(self, request):
"""
Return a string to be used as the value of the `WWW-Authenticate`
header in a `401 Unauthenticated` response, or `None` if the
authentication scheme should return `403 Permission Denied` responses.
"""
return 'Basic realm=api'

权限

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
$ cat app01/utils/permission.py

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "shuke"
# Date: 2018/6/3
from rest_framework.permissions import BasePermission


class UserPermission(BasePermission):
"""
权限验证
"""

def has_permission(self, request, view):
"""
判断是否有权限访问当前请求
Return `True` if permission is granted, `False` otherwise.
:param request:
:param view:
:return: True有权限;False无权限
"""
user_type_id = request.auth.user.user_type
if user_type_id > 0:
return True
return False

# GenericAPIView中get_object时调用
def has_object_permission(self, request, view, obj):
"""
视图继承GenericAPIView,并在其中使用get_object时获取对象时,触发单独对象权限验证
Return `True` if permission is granted, `False` otherwise.
:param request:
:param view:
:param obj:
:return: True有权限;False无权限
"""
user_type_id = request.auth.user.user_type
if user_type_id > 0:
return True
return False


class ManagerPermission(BasePermission):
"""
视图继承GenericAPIView,并在其中使用get_object时获取对象时,触发单独对象权限验证
Return `True` if permission is granted, `False` otherwise.
:param request:
:param view:
:param obj:
:return: True有权限;False无权限
"""

def has_permission(self, request, view):
user_type_id = request.auth.user.user_type
if user_type_id > 1:
return True
return False

# GenericAPIView中get_object时调用
def has_object_permission(self, request, view, obj):
"""
视图继承GenericAPIView,并在其中使用get_object时获取对象时,触发单独对象权限验证
Return `True` if permission is granted, `False` otherwise.
:param request:
:param view:
:param obj:
:return: True有权限;False无权限
"""
user_type_id = request.auth.user.user_type
if user_type_id > 1:
return True
return False

试图函数

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
from django.shortcuts import render, HttpResponse

# Create your views here.
from django.http import JsonResponse
from rest_framework.views import APIView
from app01.utils.auth import HeaderAuthentication, TokenAuthtication
from app01.utils.permission import UserPermission, ManagerPermission
from app01 import models
import uuid


class AuthView(APIView):
authentication_classes = []

def post(self, request, *args, **kwargs):
response = {'code': 1000}
user = request.data.get('username')
pwd = request.data.get('password')

obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
if not obj:
response['code'] = 1001
response['msg'] = '用户或密码错误'
return JsonResponse(response, json_dumps_params={'ensure_ascii': False})

try:
token = str(uuid.uuid4())
models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})
response['token'] = token
except Exception as e:
print("Error: ", e)
return JsonResponse(response, json_dumps_params={'ensure_ascii': False})


class UserView(APIView):
# 认证的动作是由request.user触发
authentication_classes = [HeaderAuthentication, TokenAuthtication]

# 循环执行所有的权限,当前试图只允许管理员以上权限访问
permission_classes = [ManagerPermission, ]

def get(self, request, *args, **kwargs):
print(request.user)
print(request.auth)
return HttpResponse('user.get: %s,token: %s' % (request.user, request.auth))

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

验证:
无访问权限
有访问权限

全局应用权限
上述操作中均是对单独视图进行特殊配置,如果想要对全局进行配置,则需要再配置文件中写入即可。

1
2
3
4
5
6
7
8
REST_FRAMEWORK = {
'UNAUTHENTICATED_USER': None,
'UNAUTHENTICATED_TOKEN': None,
'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.auth.HeaderAuthentication', ],
"DEFAULT_PERMISSION_CLASSES": [
"app01.utils.permission.ManagerPermission",
],
}

路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ cat urls.py

from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include

urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/(?P<version>\w+)/', include('app01.urls')),
]

$ cat app01/urls.py
from django.conf.urls import url
from app01 import views

urlpatterns = [
url(r'^auth/$', views.AuthView.as_view()),
url(r'^user/$', views.UserView.as_view()),
]

试图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ cat views.py

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


class UserView(APIView):
def get(self, request, *args, **kwargs):
# self.dispatch
print(request.user)
print(request.auth)
return Response('GET请求,响应内容')

def post(self, request, *args, **kwargs):
return Response('POST请求,响应内容')

def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容')

3.3 用户访问次数/频率限制

3.3.1 基于用户IP访问限制频率

路由

1
2
3
4
5
6
7
$ cat urls.py
from django.conf.urls import url, include
from web.views import TestView

urlpatterns = [
url(r'^test/', TestView.as_view()),
]

试图

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import time
from rest_framework.views import APIView
from rest_framework.response import Response

from rest_framework import exceptions
from rest_framework.throttling import BaseThrottle
from rest_framework.settings import api_settings

# 保存访问记录
RECORD = {
'用户IP': [12312139, 12312135, 12312133, ]
}


class TestThrottle(BaseThrottle):
ctime = time.time

def get_ident(self, request):
"""
根据用户IP和代理IP,当做请求者的唯一IP
Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR
if present and number of proxies is > 0. If not use all of
HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR.
"""
xff = request.META.get('HTTP_X_FORWARDED_FOR')
remote_addr = request.META.get('REMOTE_ADDR')
num_proxies = api_settings.NUM_PROXIES

if num_proxies is not None:
if num_proxies == 0 or xff is None:
return remote_addr
addrs = xff.split(',')
client_addr = addrs[-min(num_proxies, len(addrs))]
return client_addr.strip()

return ''.join(xff.split()) if xff else remote_addr

def allow_request(self, request, view):
"""
是否仍然在允许范围内
Return `True` if the request should be allowed, `False` otherwise.
:param request:
:param view:
:return: True,表示可以通过;False表示已超过限制,不允许访问
"""
# 获取用户唯一标识(如:IP)

# 允许一分钟访问10次
num_request = 10
time_request = 60

now = self.ctime()
ident = self.get_ident(request)
self.ident = ident
if ident not in RECORD:
RECORD[ident] = [now, ]
return True
history = RECORD[ident]
while history and history[-1] <= now - time_request:
history.pop()
if len(history) < num_request:
history.insert(0, now)
return True

def wait(self):
"""
多少秒后可以允许继续访问
Optionally, return a recommended number of seconds to wait before
the next request.
"""
last_time = RECORD[self.ident][0]
now = self.ctime()
return int(60 + last_time - now)


class TestView(APIView):
throttle_classes = [TestThrottle, ]

def get(self, request, *args, **kwargs):
# self.dispatch
print(request.user)
print(request.auth)
return Response('GET请求,响应内容')

def post(self, request, *args, **kwargs):
return Response('POST请求,响应内容')

def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容')

def throttled(self, request, wait):
"""
访问次数被限制时,定制错误信息
"""

class Throttled(exceptions.Throttled):
default_detail = '请求被限制.'
extra_detail_singular = '请 {wait} 秒之后再重试.'
extra_detail_plural = '请 {wait} 秒之后再重试.'

raise Throttled(wait)

3.3.2 基于用户IP控制访问频率(利用Django缓存)

全局配置

1
2
3
4
5
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'test_scope': '10/m',
},
}

路由

1
2
3
4
5
6
from django.conf.urls import url, include
from web.views import TestView

urlpatterns = [
url(r'^test/', TestView.as_view()),
]

试图

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
55
56
57
58
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response

from rest_framework import exceptions
from rest_framework.throttling import SimpleRateThrottle


class TestThrottle(SimpleRateThrottle):

# 配置文件定义的显示频率的Key
scope = "test_scope"

def get_cache_key(self, request, view):
"""
Should return a unique cache-key which can be used for throttling.
Must be overridden.

May return `None` if the request should not be throttled.
"""
if not request.user:
ident = self.get_ident(request)
else:
ident = request.user

return self.cache_format % {
'scope': self.scope,
'ident': ident
}


class TestView(APIView):
throttle_classes = [TestThrottle, ]

def get(self, request, *args, **kwargs):
# self.dispatch
print(request.user)
print(request.auth)
return Response('GET请求,响应内容')

def post(self, request, *args, **kwargs):
return Response('POST请求,响应内容')

def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容')

def throttled(self, request, wait):
"""
访问次数被限制时,定制错误信息
"""

class Throttled(exceptions.Throttled):
default_detail = '请求被限制.'
extra_detail_singular = '请 {wait} 秒之后再重试.'
extra_detail_plural = '请 {wait} 秒之后再重试.'

raise Throttled(wait)

3.3.3 在试图中限制请求频率

全局配置

1
2
3
4
5
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'request_scope': '10/m',
},
}

路由

1
2
3
4
5
6
from django.conf.urls import url, include
from web.views import TestView

urlpatterns = [
url(r'^test/', TestView.as_view()),
]

试图

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
55
56
57
58
59
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response

from rest_framework import exceptions
from rest_framework.throttling import ScopedRateThrottle


# 继承 ScopedRateThrottle
class TestThrottle(ScopedRateThrottle):

def get_cache_key(self, request, view):
"""
Should return a unique cache-key which can be used for throttling.
Must be overridden.

May return `None` if the request should not be throttled.
"""
if not request.user:
ident = self.get_ident(request)
else:
ident = request.user

return self.cache_format % {
'scope': self.scope,
'ident': ident
}


class TestView(APIView):
throttle_classes = [TestThrottle, ]

# 在settings中获取 xxxxxx 对应的频率限制值
throttle_scope = "xxxxxx"

def get(self, request, *args, **kwargs):
# self.dispatch
print(request.user)
print(request.auth)
return Response('GET请求,响应内容')

def post(self, request, *args, **kwargs):
return Response('POST请求,响应内容')

def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容')

def throttled(self, request, wait):
"""
访问次数被限制时,定制错误信息
"""

class Throttled(exceptions.Throttled):
default_detail = '请求被限制.'
extra_detail_singular = '请 {wait} 秒之后再重试.'
extra_detail_plural = '请 {wait} 秒之后再重试.'

raise Throttled(wait)

3.3.4 匿名时用IP限制+登录时用Token限制

全局配置

1
2
3
4
5
6
7
8
REST_FRAMEWORK = {
'UNAUTHENTICATED_USER': None,
'UNAUTHENTICATED_TOKEN': None,
'DEFAULT_THROTTLE_RATES': {
'backend_anon': '10/m',
'backend_user': '20/m',
},
}

路由

1
2
3
4
5
6
from django.conf.urls import url, include
from web.views.s3_throttling import TestView

urlpatterns = [
url(r'^test/', TestView.as_view()),
]

试图

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# !/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response

from rest_framework.throttling import SimpleRateThrottle


class BackendAnonRateThrottle(SimpleRateThrottle):
"""
匿名用户,根据IP进行限制
"""
scope = "backend_anon"

def get_cache_key(self, request, view):
# 用户已登录,则跳过 匿名频率限制
if request.user:
return None

return self.cache_format % {
'scope': self.scope,
'ident': self.get_ident(request)
}


class BackendUserRateThrottle(SimpleRateThrottle):
"""
登录用户,根据用户token限制
"""
scope = "backend_user"

def get_ident(self, request):
"""
认证成功时:request.user是用户对象;request.auth是token对象
:param request:
:return:
"""
# return request.auth.token
return "user_token"

def get_cache_key(self, request, view):
"""
获取缓存key
:param request:
:param view:
:return:
"""
# 未登录用户,则跳过 Token限制
if not request.user:
return None

return self.cache_format % {
'scope': self.scope,
'ident': self.get_ident(request)
}


class TestView(APIView):
throttle_classes = [BackendAnonRateThrottle, BackendUserRateThrottle, ]

def get(self, request, *args, **kwargs):
# self.dispatch
print(request.user)
print(request.auth)
return Response('GET请求,响应内容')

def post(self, request, *args, **kwargs):
return Response('POST请求,响应内容')

def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容')

3.3.5 全局使用

1
2
3
4
5
6
7
8
9
10
11
12
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'api.utils.throttles.throttles.BackendAnonRateThrottle',
'api.utils.throttles.throttles.BackendUserRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '10/day',
'user': '10/day',
'backend_anon': '10/m',
'backend_user': '20/m',
},
}

3.4 版本

共6个类

  1. BaseVersioning
  2. AcceptHeaderVersioning
  3. URLPathVersioning
  4. NamespaceVersioning
  5. HostNameVersioning
  6. QueryParameterVersioning
    而且还可以看到BaseVersioning类是其余5个类的父类,并且这其余的5个类,每个类中都有一个determine_version方法,在项目的视图函数中导入其中任意一个类,打印versioning_class
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    from django.shortcuts import render,HttpResponse
    from rest_framework.views import APIView
    from django.views import View
    from rest_framework.versioning import QueryParameterVersioning

    class UsersView(APIView):
    versioning_class=QueryParameterVersioning

    def get(self,request,*args,**kwargs):
    print(self.versioning_class) #打印versioning_class

    return HttpResponse("aaaa")
    输出结果:
    1
    <class 'rest_framework.versioning.QueryParameterVersioning'>
    所以versioning_class是一个类,并且versioning_class类中有一个determine_version方法

3.4.1 基于URL的GET传参方式

如:/users?version=v1
全局配置

1
2
3
4
5
REST_FRAMEWORK = {
'DEFAULT_VERSION': 'v1', # 默认版本
'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本
'VERSION_PARAM': 'version' # URL中获取值的key
}

路由

1
2
3
4
5
6
7
8
from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include
from app01.views import TestView

urlpatterns = [
url(r'^test/', TestView.as_view(), name='test'),
]

试图

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
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import QueryParameterVersioning


class TestView(APIView):
versioning_class = QueryParameterVersioning

def get(self, request, *args, **kwargs):
# 获取版本
print(request.version)
# 获取版本管理的类
print(request.versioning_scheme)

# 反向生成URL
reverse_url = request.versioning_scheme.reverse('test', request=request)
print(reverse_url)

return Response('GET请求,响应内容')

def post(self, request, *args, **kwargs):
return Response('POST请求,响应内容')

def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容')

3.4.2 基于URL的正则方式

如: /v1/users/

1
2
3
4
5
REST_FRAMEWORK = {
'DEFAULT_VERSION': 'v1', # 默认版本
'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本
'VERSION_PARAM': 'version' # URL中获取值的key
}

路由

1
2
3
4
5
6
from django.conf.urls import url, include
from web.views import TestView

urlpatterns = [
url(r'^(?P<version>[v1|v2]+)/test/', TestView.as_view(), name='test'),
]

试图

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
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import URLPathVersioning


class TestView(APIView):
versioning_class = URLPathVersioning

def get(self, request, *args, **kwargs):
# 获取版本
print(request.version)
# 获取版本管理的类
print(request.versioning_scheme)

# 反向生成URL
reverse_url = request.versioning_scheme.reverse('test', request=request)
print(reverse_url)

return Response('GET请求,响应内容')

def post(self, request, *args, **kwargs):
return Response('POST请求,响应内容')

def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容')

3.4.3 基于accept请求头方式

如:Accept: application/json; version=1.0

1
2
3
4
5
REST_FRAMEWORK = {
'DEFAULT_VERSION': 'v1', # 默认版本
'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本
'VERSION_PARAM': 'version' # URL中获取值的key
}

路由

1
2
3
4
5
6
from django.conf.urls import url, include
from web.views import TestView

urlpatterns = [
url(r'^test/', TestView.as_view(), name='test'),
]

试图

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
# !/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import AcceptHeaderVersioning


class TestView(APIView):
versioning_class = AcceptHeaderVersioning

def get(self, request, *args, **kwargs):
# 获取版本 HTTP_ACCEPT头
print(request.version)
# 获取版本管理的类
print(request.versioning_scheme)
# 反向生成URL
reverse_url = request.versioning_scheme.reverse('test', request=request)
print(reverse_url)

return Response('GET请求,响应内容,基于Accept请求头方式')

def post(self, request, *args, **kwargs):
return Response('POST请求,响应内容,基于Accept请求头方式')

def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容,基于Accept请求头方式')

3.4.4 基于主机名方式

如: v1.example.com

1
2
3
4
5
6
ALLOWED_HOSTS = ['*']
REST_FRAMEWORK = {
'DEFAULT_VERSION': 'v1', # 默认版本
'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本
'VERSION_PARAM': 'version' # URL中获取值的key
}

路由

1
2
3
4
5
6
from django.conf.urls import url, include
from web.views import TestView

urlpatterns = [
url(r'^test/', TestView.as_view(), name='test'),
]

试图

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
# !/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import HostNameVersioning


class TestView(APIView):
versioning_class = HostNameVersioning

def get(self, request, *args, **kwargs):
# 获取版本
print(request.version)
# 获取版本管理的类
print(request.versioning_scheme)
# 反向生成URL
reverse_url = request.versioning_scheme.reverse('test', request=request)
print(reverse_url)

return Response('GET请求,响应内容,基于主机名方式')

def post(self, request, *args, **kwargs):
return Response('POST请求,响应内容,基于主机名方式')

def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容,基于主机名方式')

3.4.5 基于Django路由系统的namespace

如: example.com/v1/users/

1
2
3
4
5
REST_FRAMEWORK = {
'DEFAULT_VERSION': 'v1', # 默认版本
'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本
'VERSION_PARAM': 'version' # URL中获取值的key
}

路由

1
2
3
4
5
6
7
8
9
10
11
12
from django.conf.urls import url, include
from app01.views import TestView

urlpatterns = [
url(r'^v1/', ([
url(r'test/', TestView.as_view(), name='test1')
], None, 'v1')),
url(r'^v2/', ([
url(r'test/', TestView.as_view(), name='test2')
], None, 'v2')),

]

试图

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
# !/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import NamespaceVersioning


class TestView(APIView):
authentication_classes = []
permission_classes = []
versioning_class = NamespaceVersioning

def get(self, request, *args, **kwargs):
# 获取版本
print(request.version)
# 获取版本管理的类
print(request.versioning_scheme)
# 反向生成URL
reverse_url = request.versioning_scheme.reverse('test1', request=request)
print(reverse_url)

return Response('GET请求,响应内容,基于Django路由系统的namespace')

def post(self, request, *args, **kwargs):
return Response('POST请求,响应内容,基于Django路由系统的namespace')

def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容,基于Django路由系统的namespace')
```
#### 3.4.6 全局应用

REST_FRAMEWORK = {
‘DEFAULT_VERSIONING_CLASS’:”rest_framework.versioning.URLPathVersioning”,
‘DEFAULT_VERSION’: ‘v1’,
‘ALLOWED_VERSIONS’: [‘v1’, ‘v2’],
‘VERSION_PARAM’: ‘version’
}

1
2
3
4
#### 3.4.7 自定义版本控制方案
要实现自定义版本控制方案,请继承 BaseVersioning并覆盖 .determine_version 方法。
举个栗子
以下示例使用自定义的 X-API-Version header 来确定所请求的版本

class XAPIVersionScheme(versioning.BaseVersioning):
def determine_version(self, request, args, *kwargs):
return request.META.get(‘HTTP_X_API_VERSION’, None)

1
2
3
4
5
6
如果你的版本控制方案基于请求 URL,则还需要更改版本化 URL 的确定方式。为了做到这一点,你应该重写类的 .reverse()方法。有关示例,请参阅源代码。

### 3.5 解析器(parser)
根据请求头content-type选择对应的解析器就请求体内容进行处理
#### 3.5.1 仅处理请求头content-type为application/json的请求体
路由

from django.conf.urls import url, include
from web.views.s5_parser import TestView

urlpatterns = [
url(r’test/‘, TestView.as_view(), name=’test’),
]

1
试图

!/usr/bin/env python

-- coding:utf-8 --

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

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

def post(self, request, *args, **kwargs):
    print(request.content_type)

    # 获取请求的值,并使用对应的JSONParser进行处理
    print(request.data)

    # application/x-www-form-urlencoded 或 multipart/form-data时,request.POST中才有值
    print(request.POST)
    print(request.FILES)

    return Response('POST请求,请求内容: %s' % request.data)

def put(self, request, *args, **kwargs):
    return Response('PUT请求,响应内容,解析器')
1
2
#### 3.5.2 仅处理请求头content-type为application/x-www-form-urlencoded的请求体
路由

from django.conf.urls import url, include
from web.views import TestView

urlpatterns = [
url(r’test/‘, TestView.as_view(), name=’test’),
]

1
试图

#!/usr/bin/env python

-- coding:utf-8 --

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework.parsers import FormParser

class TestView(APIView):
parser_classes = [FormParser, ]

def post(self, request, *args, **kwargs):
    print(request.content_type)

    # 获取请求的值,并使用对应的JSONParser进行处理
    print(request.data)

    # application/x-www-form-urlencoded 或 multipart/form-data时,request.POST中才有值
    print(request.POST)
    print(request.FILES)

    return Response('POST请求,响应内容')

def put(self, request, *args, **kwargs):
    return Response('PUT请求,响应内容')
1
2
#### 3.5.3 仅处理请求头content-type为multipart/form-data的请求体
路由

from django.conf.urls import url, include
from web.views import TestView

urlpatterns = [
url(r’test/‘, TestView.as_view(), name=’test’),
]

1
试图

!/usr/bin/env python

-- coding:utf-8 --

from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser

class TestView(APIView):
authentication_classes = []
permission_classes = []
parser_classes = [MultiPartParser, ]

def get(self, request, *args, **kwargs):
    return render(request, 'test.html')

def post(self, request, *args, **kwargs):
    print(request.content_type)

    # 获取请求的值,并使用对应的JSONParser进行处理
    print(request.data)
    # application/x-www-form-urlencoded 或 multipart/form-data时,request.POST中才有值
    print(request.POST)
    print(request.FILES)
    return Response('POST请求,响应内容')

def put(self, request, *args, **kwargs):
    return Response('PUT请求,响应内容')
1
模版文件
Title
<input type="submit" value="提交">
1
2
#### 3.5.4 仅上传文件
路由
from django.conf.urls import url, include from web.views import TestView

urlpatterns = [
url(r’test/(?P[^/]+)’, TestView.as_view(), name=’test’),
]

1
试图

!/usr/bin/env python

-- coding:utf-8 --

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

class TestView(APIView):
authentication_classes = []
permission_classes = []
parser_classes = [FileUploadParser, ]

def get(self, request, *args, **kwargs):
    return render(request, 'test.html')

def post(self, request, filename, *args, **kwargs):
    print(filename)
    print(request.content_type)

    # 获取请求的值,并使用对应的JSONParser进行处理
    print(request.data)
    # application/x-www-form-urlencoded 或 multipart/form-data时,request.POST中才有值
    print(request.POST)
    print(request.FILES)
    return Response('POST请求,响应内容')

def put(self, request, *args, **kwargs):
    return Response('PUT请求,响应内容')
1
模版文件
Title
<input type="submit" value="提交">
1
2
3
#### 3.5.5 同时多个Parser
当同时使用多个parser时,rest framework会根据请求头content-type自动进行比对,并使用对应parser
路由
from django.conf.urls import url, include from web.views import TestView

urlpatterns = [
url(r’test/‘, TestView.as_view(), name=’test’),
]

1
试图

!/usr/bin/env python

-- coding:utf-8 --

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import JSONParser, FormParser, MultiPartParser

class TestView(APIView):
parser_classes = [JSONParser, FormParser, MultiPartParser, ]

def post(self, request, *args, **kwargs):
    print(request.content_type)

    # 获取请求的值,并使用对应的JSONParser进行处理
    print(request.data)
    # application/x-www-form-urlencoded 或 multipart/form-data时,request.POST中才有值
    print(request.POST)
    print(request.FILES)
    return Response('POST请求,响应内容')

def put(self, request, *args, **kwargs):
    return Response('PUT请求,响应内容')
1
#### 3.5.6 全局应用

REST_FRAMEWORK = {
‘DEFAULT_PARSER_CLASSES’:[
‘rest_framework.parsers.JSONParser’
‘rest_framework.parsers.FormParser’
‘rest_framework.parsers.MultiPartParser’
]

}

1
路由

from django.conf.urls import url, include
from web.views import TestView

urlpatterns = [
url(r’test/‘, TestView.as_view(), name=’test’),
]

1
试图

#!/usr/bin/env python

-- coding:utf-8 --

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

class TestView(APIView):
def post(self, request, args, *kwargs):
print(request.content_type)

    # 获取请求的值,并使用对应的JSONParser进行处理
    print(request.data)
    # application/x-www-form-urlencoded 或 multipart/form-data时,request.POST中才有值
    print(request.POST)
    print(request.FILES)
    return Response('POST请求,响应内容')

def put(self, request, *args, **kwargs):
    return Response('PUT请求,响应内容')
1
2
3
4
5
6
⚠️ 个别特殊的值可以通过Django的request对象 request\.\_request 来进行获取

### 3.6 序列化
序列化用于对用户请求数据进行验证和数据进行序列化
#### 3.6.1 自定义字段
路由

from django.conf.urls import url, include
from app01.views import TestView

urlpatterns = [
url(r’^test/‘, TestView.as_view(), name=’test’),
]

1
models

from django.db import models

Create your models here.

from django.db import models

class UserInfo(models.Model):
user_type_choices = (
(1, ‘普通用户’),
(2, ‘管理员’),
(3, ‘超级管理员’),
)
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
user_type = models.IntegerField(choices=user_type_choices, default=1)

class UserToken(models.Model):
user = models.OneToOneField(‘UserInfo’, on_delete=True)
token = models.CharField(max_length=64)

1
试图

!/usr/bin/env python

-- coding:utf-8 --

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from . import models

class PasswordValidator(object):
def init(self, base):
self.base = base

def __call__(self, value):
    if value != self.base:
        message = 'This field must be %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 UserSerializer(serializers.Serializer):
user_type = serializers.IntegerField()
username = serializers.CharField(min_length=3)
password = serializers.CharField(error_messages={‘required’: ‘密码不能为空’}, validators=[PasswordValidator(‘666’)])

class TestView(APIView):
authentication_classes = []
permission_classes = []

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

    # 序列化,将数据库查询字段序列化为字典
    data_list = models.UserInfo.objects.all()
    ser = UserSerializer(instance=data_list, many=True)
    # 或
    # obj = models.UserInfo.objects.all().first()
    # ser = UserSerializer(instance=obj, many=False)
    return Response(ser.data)

def post(self, request, *args, **kwargs):
    # 验证,对请求发来的数据进行验证
    ser = UserSerializer(data=request.data)
    if ser.is_valid():
        print(ser.validated_data)
    else:
        print(ser.errors)

    return Response('POST请求,响应内容')
1
POST验证:

curl -X POST
http://127.0.0.1:8001/test/
-H ‘Cache-Control: no-cache’
-H ‘Content-Type: application/json’
-H ‘Postman-Token: 4ea5d1d1-e3b1-38a3-19f6-25070ace2342’
-d ‘{
“user_type”: 2,
“username”: “python”,
“password”: “666”
}’

1
2
#### 3.6.2 基于Model自动生成字段
路由

from django.conf.urls import url, include
from web.views.s6_serializers import TestView

urlpatterns = [
url(r’test/‘, TestView.as_view(), name=’test’),
]

1
试图

#!/usr/bin/env python

-- coding:utf-8 --

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from .. import models

class PasswordValidator(object):
def init(self, base):
self.base = str(base)

def __call__(self, value):
    if value != self.base:
        message = 'This field must be %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 ModelUserSerializer(serializers.ModelSerializer):

user = serializers.CharField(max_length=32)

class Meta:
    model = models.UserInfo
    fields = "__all__"
    # fields = ['user', 'pwd', 'ut']
    depth = 2
    extra_kwargs = {'user': {'min_length': 6}, 'pwd': {'validators': [PasswordValidator(666), ]}}
    # read_only_fields = ['user']

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

    # 序列化,将数据库查询字段序列化为字典
    data_list = models.UserInfo.objects.all()
    ser = ModelUserSerializer(instance=data_list, many=True)
    # 或
    # obj = models.UserInfo.objects.all().first()
    # ser = UserSerializer(instance=obj, many=False)
    return Response(ser.data)

def post(self, request, *args, **kwargs):
    # 验证,对请求发来的数据进行验证
    print(request.data)
    ser = ModelUserSerializer(data=request.data)
    if ser.is_valid():
        print(ser.validated_data)
    else:
        print(ser.errors)

    return Response('POST请求,响应内容')
1
2
#### 3.6.3 生成URL
路由

from django.conf.urls import url, include
from web.views.s6_serializers import TestView

urlpatterns = [
url(r’test/‘, TestView.as_view(), name=’test’),
url(r’detail/(?P\d+)/‘, TestView.as_view(), name=’detail’),
]

1
models

from django.db import models

Create your models here.

from django.db import models

class UserInfo(models.Model):
user_type_choices = (
(1, ‘普通用户’),
(2, ‘管理员’),
(3, ‘超级管理员’),
)
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
user_type = models.IntegerField(choices=user_type_choices, default=1)

class UserToken(models.Model):
user = models.OneToOneField(‘UserInfo’, on_delete=True)
token = models.CharField(max_length=64)

1
试图

!/usr/bin/env python

-- coding:utf-8 --

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from . import models

class PasswordValidator(object):
def init(self, base):
self.base = str(base)

def __call__(self, value):
    if value != self.base:
        message = 'This field must be %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 ModelUserSerializer(serializers.ModelSerializer):
user_type = serializers.HyperlinkedIdentityField(view_name=’detail’)

class Meta:
    model = models.UserInfo
    fields = "__all__"

    extra_kwargs = {
        'username': {'min_length': 6},
        'pasword': {'validators': [PasswordValidator(666), ]},
    }

class TestView(APIView):
authentication_classes = []
permission_classes = []

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

    # 序列化,将数据库查询字段序列化为字典
    data_list = models.UserInfo.objects.all()
    ser = ModelUserSerializer(instance=data_list, many=True, context={'request': request})
    # 或
    # obj = models.UserInfo.objects.all().first()
    # ser = UserSerializer(instance=obj, many=False)
    return Response(ser.data)

def post(self, request, *args, **kwargs):
    # 验证,对请求发来的数据进行验证
    print(request.data)
    ser = ModelUserSerializer(data=request.data)
    if ser.is_valid():
        print(ser.validated_data)
    else:
        print(ser.errors)

    return Response('POST请求,响应内容')
1
2
3
4
5
请求如下图所示:
![images](https://note.youdao.com/yws/api/personal/file/WEBe8cf0d66e9af86eaccbbfdc47f098d67?method=download&shareKey=cff9deb39e5ad27072b3d2ccae7b1307)

#### 3.6.4 自动生成URL
路由

from django.conf.urls import url, include
from app01.views import TestView

urlpatterns = [
url(r’test/‘, TestView.as_view(), name=’test’),
url(r’detail/(?P\d+)/‘, TestView.as_view(), name=’detail-info’),
]

1
试图

!/usr/bin/env python

-- coding:utf-8 --

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from . import models

class PasswordValidator(object):
def init(self, base):
self.base = str(base)

def __call__(self, value):
    if value != self.base:
        message = 'This field must be %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 ModelUserSerializer(serializers.HyperlinkedModelSerializer):
ll = serializers.HyperlinkedIdentityField(view_name=’detail-info’)
tt = serializers.CharField(required=False)

class Meta:
    model = models.UserInfo
    fields = "__all__"
    list_serializer_class = serializers.ListSerializer

    extra_kwargs = {
        'username': {'min_length': 6},
        'password': {'validators': [PasswordValidator(666), ]},
        'url': {'view_name': 'detail-info'},
        'ut': {'view_name': 'detail-info'},
    }

class TestView(APIView):
def get(self, request, args, *kwargs):
# # 序列化,将数据库查询字段序列化为字典
data_list = models.UserInfo.objects.all()
ser = ModelUserSerializer(instance=data_list, many=True, context={‘request’: request})
# # 如果Many=True
# # 或
# # obj = models.UserInfo.objects.all().first()
# # ser = UserSerializer(instance=obj, many=False)
return Response(ser.data)

def post(self, request, *args, **kwargs):
    # 验证,对请求发来的数据进行验证
    print(request.data)
    ser = ModelUserSerializer(data=request.data)
    if ser.is_valid():
        print(ser.validated_data)
    else:
        print(ser.errors)

    return Response('POST请求,响应内容')
1
2
3
#### 3.7 分页
#### 3.7.1 根据页码进行分页
路由

from django.conf.urls import url, include
from app01.views import UserViewSet

urlpatterns = [
url(r’test/‘, UserViewSet.as_view(), name=’test’),
]

1
试图

!/usr/bin/env python

-- coding:utf-8 --

from rest_framework.views import APIView
from rest_framework import serializers
from . import models

from rest_framework.pagination import PageNumberPagination

class StandardResultsSetPagination(PageNumberPagination):
# 默认每页显示的数据条数
page_size = 1
# 获取URL参数中设置的每页显示数据条数
page_size_query_param = ‘page_size’

# 获取URL参数中传入的页码key
page_query_param = 'page'

# 最大支持的每页显示的数据条数
max_page_size = 1

class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = “all

class UserViewSet(APIView):
def get(self, request, args, *kwargs):
user_list = models.UserInfo.objects.all().order_by(‘-id’)

# 实例化分页对象,获取数据库中的分页数据
paginator = StandardResultsSetPagination()
page_user_list = paginator.paginate_queryset(user_list, self.request, view=self)

# 序列化对象
serializer = UserSerializer(page_user_list, many=True)

# 生成分页和数据
response = paginator.get_paginated_response(serializer.data)
return response
1
访问验证:

http://127.0.0.1:8001/test/?page=1
http://127.0.0.1:8001/test/?page=2

1
2
#### 3.7.2 位置和个数进行分页
路由

from django.conf.urls import url, include
from app01.views import UserViewSet

urlpatterns = [
url(r’test/‘, UserViewSet.as_view(), name=’test’),
]

1
试图

!/usr/bin/env python

-- coding:utf-8 --

from rest_framework.views import APIView
from rest_framework import serializers
from . import models

from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination

class StandardResultsSetPagination(LimitOffsetPagination):
# 默认每页显示的数据条数
default_limit = 2
# URL中传入的显示数据条数的参数
limit_query_param = ‘limit’
# URL中传入的数据位置的参数
offset_query_param = ‘offset’
# 最大每页显得条数
max_limit = None

class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = “all

class UserViewSet(APIView):
def get(self, request, args, *kwargs):
user_list = models.UserInfo.objects.all().order_by(‘-id’)

# 实例化分页对象,获取数据库中的分页数据
paginator = StandardResultsSetPagination()
page_user_list = paginator.paginate_queryset(user_list, self.request, view=self)

# 序列化对象
serializer = UserSerializer(page_user_list, many=True)

# 生成分页和数据
response = paginator.get_paginated_response(serializer.data)
return response
1
2
3
4
5
验证:
![位置分页](https://note.youdao.com/yws/api/personal/file/WEBdebc8cfb0c33b1533d1e041ed9925422?method=download&shareKey=2b1d435332858dbe629d4c3d78b670d8)

#### 3.7.3 游标分页
路由

from django.conf.urls import url, include
from app01.views import UserViewSet

urlpatterns = [
url(r’test/‘, UserViewSet.as_view(), name=’test’),
]

1
试图

!/usr/bin/env python

-- coding:utf-8 --

from rest_framework.views import APIView
from rest_framework import serializers
from . import models

from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination

class StandardResultsSetPagination(CursorPagination):
# URL传入的游标参数
cursor_query_param = ‘cursor’
# 默认每页显示的数据条数
page_size = 10
# URL传入的每页显示条数的参数
page_size_query_param = ‘page_size’
# 每页显示数据最大条数
max_page_size = 1000

# 根据ID从大到小排列
ordering = "id"

class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = “all

class UserViewSet(APIView):
def get(self, request, args, *kwargs):
user_list = models.UserInfo.objects.all().order_by(‘-id’)

# 实例化分页对象,获取数据库中的分页数据
paginator = StandardResultsSetPagination()
page_user_list = paginator.paginate_queryset(user_list, self.request, view=self)

# 序列化对象
serializer = UserSerializer(page_user_list, many=True)

# 生成分页和数据
response = paginator.get_paginated_response(serializer.data)
return response
1
2
3
4

### 3.8 路由系统
#### 3.8.1 自定义路由
路由

from django.conf.urls import url, include
from web.views import s11_render

urlpatterns = [
url(r’^test/$’, s11_render.TestView.as_view()),
url(r’^test.(?P[a-z0-9]+)$’, s11_render.TestView.as_view()),
url(r’^test/(?P[^/.]+)/$’, s11_render.TestView.as_view()),
url(r’^test/(?P[^/.]+).(?P[a-z0-9]+)$’, s11_render.TestView.as_view())
]

1
试图

from rest_framework.views import APIView
from rest_framework.response import Response
from .. import models

class TestView(APIView):
def get(self, request, args, *kwargs):
print(kwargs)
print(self.renderer_classes)
return Response(‘…’)

1
2
#### 3.8.2 半自动路由
路由

from django.conf.urls import url, include
from web.views import s10_generic

urlpatterns = [
url(r’^test/$’, s10_generic.UserViewSet.as_view({‘get’: ‘list’, ‘post’: ‘create’})),
url(r’^test/(?P\d+)/$’, s10_generic.UserViewSet.as_view(
{‘get’: ‘retrieve’, ‘put’: ‘update’, ‘patch’: ‘partial_update’, ‘delete’: ‘destroy’})),
]

1
试图

#!/usr/bin/env python

-- coding:utf-8 --

from rest_framework.viewsets import ModelViewSet
from rest_framework import serializers
from .. import models

class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = “all

class UserViewSet(ModelViewSet):
queryset = models.UserInfo.objects.all()
serializer_class = UserSerializer

1
2
#### 3.8.3 全自动路由
路由

from django.conf.urls import url, include
from rest_framework import routers
from web.views import s10_generic

router = routers.DefaultRouter()
router.register(r’users’, s10_generic.UserViewSet)

urlpatterns = [
url(r’^’, include(router.urls)),
]

1
试图

from rest_framework.viewsets import ModelViewSet
from rest_framework import serializers
from .. import models

class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = “all

class UserViewSet(ModelViewSet):
queryset = models.UserInfo.objects.all()
serializer_class = UserSerializer

1
2
3
### 3.9 试图
#### 3.9.1 GenericViewSet
路由

from django.conf.urls import url, include
from web.views.s7_viewset import TestView

urlpatterns = [
url(r’test/‘, TestView.as_view({‘get’:’list’}), name=’test’),
url(r’detail/(?P\d+)/‘, TestView.as_view({‘get’:’list’}), name=’xxxx’),
]

1
试图

#!/usr/bin/env python

-- coding:utf-8 --

from rest_framework import viewsets
from rest_framework.response import Response

class TestView(viewsets.GenericViewSet):
def list(self, request, args, *kwargs):
return Response(‘…’)

def add(self, request, *args, **kwargs):
    pass

def delete(self, request, *args, **kwargs):
    pass

def edit(self, request, *args, **kwargs):
    pass
1
2
#### 3.9.2 ModelViewSet(自定义URL)
路由

from django.conf.urls import url, include
from web.views import s10_generic

urlpatterns = [
url(r’^test/$’, s10_generic.UserViewSet.as_view({‘get’: ‘list’, ‘post’: ‘create’})),
url(r’^test/(?P\d+)/$’, s10_generic.UserViewSet.as_view(
{‘get’: ‘retrieve’, ‘put’: ‘update’, ‘patch’: ‘partial_update’, ‘delete’: ‘destroy’})),
]

1
试图

#!/usr/bin/env python

-- coding:utf-8 --

from rest_framework.viewsets import ModelViewSet
from rest_framework import serializers
from .. import models

class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = “all

class UserViewSet(ModelViewSet):
queryset = models.UserInfo.objects.all()
serializer_class = UserSerializer

1
2
#### 3.9.3 ModelViewSet(rest framework路由)
路由

from django.conf.urls import url, include
from rest_framework import routers
from app01 import views

router = routers.DefaultRouter()
router.register(r’users’, views.UserViewSet)
router.register(r’groups’, views.GroupViewSet)

Wire up our API using automatic URL routing.

Additionally, we include login URLs for the browsable API.

urlpatterns = [
url(r’^’, include(router.urls)),
]

1
试图

from rest_framework import viewsets
from rest_framework import serializers

class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = models.User
fields = (‘url’, ‘username’, ‘email’, ‘groups’)

class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = models.Group
fields = (‘url’, ‘name’)

class UserViewSet(viewsets.ModelViewSet):
“””
API endpoint that allows users to be viewed or edited.
“””
queryset = User.objects.all().order_by(‘-date_joined’)
serializer_class = UserSerializer

class GroupViewSet(viewsets.ModelViewSet):
“””
API endpoint that allows groups to be viewed or edited.
“””
queryset = Group.objects.all()
serializer_class = GroupSerializer

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

### 3.10 渲染器
根据用户请求URL或用户可接受的类型,筛选出合适的渲染组件
用户请求URL:
- http://127.0.0.1:8000/test/?format=json
- http://127.0.0.1:8000/test.json
用户请求头:
- Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

#### 3.10.1 Json
访问URL:
- http://127.0.0.1:8000/test/?format=json
- http://127.0.0.1:8000/test.json
- http://127.0.0.1:8000/test/
路由:

from django.conf.urls import url, include
from web.views import s11_render

urlpatterns = [
url(r’^test/$’, s11_render.TestView.as_view()),
url(r’^test.(?P[a-z0-9]+)’, s11_render.TestView.as_view()),
]

1
试图:

#!/usr/bin/env python

-- coding:utf-8 --

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers

from rest_framework.renderers import JSONRenderer

from .. import models

class TestSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = “all

class TestView(APIView):
renderer_classes = [JSONRenderer, ]

def get(self, request, *args, **kwargs):
    user_list = models.UserInfo.objects.all()
    ser = TestSerializer(instance=user_list, many=True)
    return Response(ser.data)
1
2
3
4
5
6
#### 3.10.2 表格
访问URL:
- http://127.0.0.1:8000/test/?format=admin
- http://127.0.0.1:8000/test.admin
- http://127.0.0.1:8000/test/
试图:

#!/usr/bin/env python

-- coding:utf-8 --

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework.renderers import AdminRenderer
from . import models

class TestSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = “all

class TestView(APIView):
renderer_classes = [AdminRenderer, ]

def get(self, request, *args, **kwargs):
    user_list = models.UserInfo.objects.all()
    ser = TestSerializer(instance=user_list, many=True)
    return Response(ser.data)
1
2
3
4
5
6
#### 3.10.3 Form表单
访问URL:
- http://127.0.0.1:8000/test/?format=form
- http://127.0.0.1:8000/test.form
- http://127.0.0.1:8000/test/
试图:

#!/usr/bin/env python

-- coding:utf-8 --

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers

from rest_framework.renderers import JSONRenderer
from rest_framework.renderers import AdminRenderer
from rest_framework.renderers import HTMLFormRendere
from . import models

class TestSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = “all

class TestView(APIView):
renderer_classes = [HTMLFormRenderer, ]

def get(self, request, *args, **kwargs):
    user_list = models.UserInfo.objects.all().first()
    ser = TestSerializer(instance=user_list, many=False)
    return Response(ser.data)
1
2
3
4
5
6
#### 3.10.4 自定义显示模版
访问URL:
- http://127.0.0.1:8000/test/?format=html
- http://127.0.0.1:8000/test.html
- http://127.0.0.1:8000/test/
路由

from django.conf.urls import url, include
from web.views import s11_render

urlpatterns = [
url(r’^test/$’, s11_render.TestView.as_view()),
url(r’^test.(?P[a-z0-9]+)’, s11_render.TestView.as_view()),
]

1
试图

#!/usr/bin/env python

-- coding:utf-8 --

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework.renderers import TemplateHTMLRenderer
from . import models

class TestSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = “all

class TestView(APIView):
renderer_classes = [TemplateHTMLRenderer, ]

def get(self, request, *args, **kwargs):
    user_list = models.UserInfo.objects.all().first()
    ser = TestSerializer(instance=user_list, many=False)
    return Response(ser.data, template_name='user_detail.html')
1
模版

$ cat user_detail.html

Title
1
2
3
4
5
6
#### 3.10.5 浏览器API+JSON
访问URL:
- http://127.0.0.1:8000/test/?format=api
- http://127.0.0.1:8000/test.api
- http://127.0.0.1:8000/test/
试图:
#!/usr/bin/env python # -*- coding:utf-8 -*- from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import serializers from rest_framework.renderers import JSONRenderer from rest_framework.renderers import BrowsableAPIRenderer from . import models

class TestSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = “all

class CustomBrowsableAPIRenderer(BrowsableAPIRenderer):
def get_default_renderer(self, view):
return JSONRenderer()

class TestView(APIView):
renderer_classes = [CustomBrowsableAPIRenderer, ]

def get(self, request, *args, **kwargs):
    user_list = models.UserInfo.objects.all().first()
    ser = TestSerializer(instance=user_list, many=False)
    return Response(ser.data, template_name='user_detail.html')
注意: 如果同时多个存在时,自动根据URL后缀来选择渲染器


[原文地址](http://www.cnblogs.com/wupeiqi/articles/7805382.html)





本文标题:什么是RESTful API以及Django RestFramework

文章作者:shuke

发布时间:2020年04月20日 - 14:04

最后更新:2020年04月20日 - 14:04

原始链接:https://shuke163.github.io/2020/04/20/%E4%BB%80%E4%B9%88%E6%98%AFRESTful-API%E4%BB%A5%E5%8F%8ADjango-RestFramework/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------本文结束感谢您的阅读-------------

本文标题:什么是RESTful API以及Django RestFramework

文章作者:shuke

发布时间:2020年04月20日 - 14:04

最后更新:2020年04月20日 - 14:04

原始链接:https://shuke163.github.io/2020/04/20/%E4%BB%80%E4%B9%88%E6%98%AFRESTful-API%E4%BB%A5%E5%8F%8ADjango-RestFramework/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

0%