urlpatterns = [
url(r'^user/$', views.user),
url(r'^user/add/$', views.user_add),
url(r'^user/edit/(\d+)/$', views.user_edit),
url(r'^user/del/(\d+)/$', views.user_del),
]
传统的视图函数方式,API接口太多,难以维护。
urlpatterns = [
url(r'user/$', views.UserView.as_view()), # GET, POST
url(r'user/(\d+)$', views.UserView.as_view()), # PUT, DELETE
]
根据请求方式的不同,执行视图类中对应的方法。同样是实现增删改查,url少一半。这也是面向资源编程的方式,特点是url中都是名词。
CBV相关知识参考:http://blog.csdn.net/ayhan_huang/article/details/78036501#t11
大神说:API与用户的通信协议,总是使用HTTPs协议
http://api.example.com
尽量使用专用的二级域名 http://www.example.com/api/
路由分发。如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。对应前后端分离的项目,可以这样分配:
前端VUE项目使用域名:http://www.example.com
后端API使用域名:http://api.example.com
应该将API的版本号放入URL。
比如:https://www.example.com/api/v1/
v1是版本信息
路径又称”终点”(endpoint),表示API的具体网址。
在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的”集合”(collection),所以API中的名词也应该使用复数。
举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。
https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees
比如:
HTTP状态码负责表示客户端HTTP请求的返回结果,标记服务端的处理是否正常,通知出现的错误等工作。
状态码 | 类别 | 原因短语 |
---|---|---|
1XX | Informational 信息性状态码 | 接收的请求正在处理 |
2XX | Success 成功状态码 | 请求正常处理完毕 |
3XX | Redirection 重定向状态码 | 需要进行附加操作以完成请求 |
4XX | Client Error 客户端错误状态码 | 服务器无法处理请求 |
5XX | Server Error 服务器错误状态码 | 服务器处理请求出错 |
状态码有限,可以再约定code,表示更细的状态:
def get(self, request, *args, **kwargs):
res = {'code': 1001, 'error': None}
try:
print('do something...')
except Exception as e:
res['error'] = str(e)
return JsonResponse(res, status=500)
如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。
{
error: "Invalid API key"
}
提供error key,显示详细错误信息
如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果
针对不同操作,服务器向用户返回的结果应该符合以下规范。
RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。
{"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的网址列表:
{
"current_user_url": "https://api.github.com/user",
"authorizations_url": "https://api.github.com/authorizations",
# ...
}
从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果:
{
"message": "Requires authentication",
"documentation_url": "https://developer.github.com/v3"
}
上面代码表示,服务器给出了提示信息,以及文档的网址。
通过Django本身也可以实现API设计,只是相对要麻烦些。Django REST framework基于Django进行了丰富,能更方便的实现API设计。
INSTALLED_APPS = [
# ...
'rest_framework',
]
urlpatterns = [
url(r'user/$', views.UserView.as_view()), # GET, POST
url(r'user/(?P\d+)/$' , views.UserView.as_view()), # PUT, DELETE
]
from rest_framework.views import APIView
from django.http import JsonResponse
class UsersView(APIView):
def dispatch(self, request, *args, **kwargs):
"""请求到来之后,首先执行dispatch方法,dispatch方法根据请求方式的不同,反射执行 get/post/put等方法"""
return super().dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
res = {
'code': '10001',
'data': [], # 字典元素
'error': None
}
# return HttpResponse(json.dumps(res), status=200, content_type='application/json')
# 如果是HttpResponse,需要手动json, 并且指定content_type
return JsonResponse(res, status=200)
def post(self, request, *args, **kwargs):
pass
def put(self, request, *args, **kwargs):
pk = kwargs.get('pk') # 获取url命名分组传参
pass
def delete(self, request, *args, **kwargs):
pass
中间件
路由系统
return csrf_exempt(view)
CBV视图类
执行dispatch方法
二次封装request
def initialize_request(self, request, *args, **kwargs):
parser_context = self.get_parser_context(request) #
return Request(
request,
parsers=self.get_parsers(), # 解析器
authenticators=self.get_authenticators(), # 认证
negotiator=self.get_content_negotiator(), # 选择器
parser_context=parser_context # 字典:view和参数
)
try:
获取版本,认证,权限,节流
def initial(self, request, *args, **kwargs):
"""
Runs anything that needs to occur prior to calling the method handler.
"""
self.format_kwarg = self.get_format_suffix(**kwargs)
# Perform content negotiation and store the accepted info on the request
# 根据用户请求选择
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
# Determine the API version, if versioning is in use.
# 获取版本信息,和处理版本的类的对象
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
# Ensure that the incoming request is permitted
# 认证
self.perform_authentication(request)
# 权限
self.check_permissions(request)
# 控制访问次数(每天访问10次)
self.check_throttles(request)
根据请求方法反射执行 GET/POST/DELETE…
except:
返回响应
查看源码可知,Django REST framework一共支持5种版本控制方式:
导入及使用方式:
from rest_framework.versioning import URLPathVersioning
class TestView(APIView):
versioning_class = URLPathVersioning # 指定版本
pass
版本控制中通用的settings全局配置:
REST_FRAMEWORK = {
# 'DEFAULT_VERSION': 'v1', # 默认版本
# 'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本
}
下面介绍其中两种比较常用获取版本的方式。
REST_FRAMEWORK = {
'VERSION_PARM': 'version' # 配置从URL中获取值的key
}
urlpatterns = [
url(r'test', views.TestView.as_view(), name='test')
]
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 xxxxxx')
"""
浏览器访问:http://127.0.0.1:8866/test/?version=v1
打印结果:
v1
http://127.0.0.1:8866/test?version=v1
"""
urlpatterns = [
url(r'test/(?P[v1|v2]+)/$' , views.TestView.as_view(), name='test')
]
# 传参必须是 v1 或 v2
更换 versioning_class 为 URLPathVersioning 即可
from rest_framework.versioning import URLPathVersioning
class TestView(APIView):
versioning_class = URLPathVersioning # 指定版本
def get(self, request, *args, **kwargs):
pass
"""
浏览器访问:http://127.0.0.1:8866/test/v2/
"""
REST framework自带了认证方式:
以及它们的基类 BaseAuthentication,通过派生BaseAuthentication 并实现其中的方法,我们可以自定义认证类,下面我们先简单体会一下
from rest_framework.views import APIView
from django.http import JsonResponse
from rest_framework.response import Response
# 认证相关
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions
TOKEN_LIST = [ #定义token,稍后我们会随机生成它
'hello',
'world'
]
class CustomAuthentication(BaseAuthentication):
"""自定义认证类"""
def authenticate(self, request): # 接口约束
token = request._request.GET.get('tk')
if token in TOKEN_LIST:
return ('lena', None)
# return None # 支持匿名用户
raise exceptions.AuthenticationFailed('认证失败') # 不允许匿名用户,交给dispatch中的异常处理
class TestView(APIView):
versioning_class = URLPathVersioning # 指定版本
authentication_classes = [CustomAuthentication,] # 指定认证方式;
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
pass
return Response('get xxxxxx')
# http://127.0.0.1:8866/test/v2/?tk=hello --> 认证成功
# http://127.0.0.1:8866/test/v2/?tk=hello888 --> 认证失败 HTTP 403 Forbidden (dispatch中异常处理返回值)
和model关联起来,根据用户名实时生成token,用户登录成功后,拿着token访问需要认证的api
from django.db import models
class UserInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
email = models.EmailField()
user_type_choices = [
(1, '普通用户'),
(2, '版主'),
(3, '管理员'),
]
user_type = models.IntegerField(choices=user_type_choices, default=1)
class Token(models.Model):
user = models.OneToOneField(to=UserInfo) # 一对一关系
token = models.CharField(max_length=64)
说明:
urlpatterns = [
url(r'api/(?P[v1|v2]+)/auth/$' , views.AuthView.as_view(), name='auth') # 登录认证
]
def generate_token(username):
"""根据用户名和时间,进行MD5值"""
import time
import hashlib
md5 = hashlib.md5(username.encode('utf-8'))
md5.update(str(time.time()).encode('utf-8'))
return md5.hexdigest()
class AuthView(APIView):
def post(self, request, *args, **kwargs):
res = {'code': 1000, # code: 1000 登录成功;1001登录失败
'msg': None, # 错误信息
'token': None}
username = request._request.POST.get('username')
pwd = request._request.POST.get('pwd')
print('usernaem:',username)
print('pwd:',pwd)
user_obj = models.UserInfo.objects.filter(username=username, password=pwd).first()
if user_obj:
# 如果用户存在,那么生成token并更新
token = generate_token(username)
models.Token.objects.update_or_create(user=user_obj, defaults={'token': token})
res['token'] = token
else:
res['code'] = 1001
res['msg'] = '用户名或密码错误'
return JsonResponse(res)
说明:
query_params
属性访问request._request.GET
,data
属性访问request._request.POST
class CustomAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get('tk')
token_obj = models.Token.objects.filter(token=token).first()
if token_obj:
# 返回(用户对象,token对象)
return (token_obj.user, token_obj)
# return None # 支持匿名用户
raise exceptions.AuthenticationFailed('认证失败') # 不允许匿名用户,交给dispatch中的异常处理
class TestView(APIView):
versioning_class = URLPathVersioning # 指定版本
authentication_classes = [CustomAuthentication, ] # 指定认证方式
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
# 认证Ok, 打印用户信息
print(request.user.username)
print(request.user.email)
return Response('get xxxxxx')
""" 认证成功,打印出用户信息
lena
[email protected]
"""
说明:TestView中之所以能request.user.username
,是因为认证类对象执行authenticate
方法返回的元组,被赋值给了Request对象:self.user, self.auth = user_auth_tuple
import requests
api = 'http://127.0.0.1:8866/api/v1/auth/'
response = requests.post(url=api, data={'username': 'lena', 'pwd': '123'})
print(response.text)
"""
{"code": 1000, "msg": null, "token": "117d16c0b1c9397a0573c28b67dad6f8"}
"""
访问api,认证成功,收到服务端返回的信息,其中包括token,以后只需要携带token就可以访问需要认证的api
用之前登录成功返回的token访问目标api
api = 'http://127.0.0.1:8866/api/v1/test/'
response2 = requests.get(url=api, params={'tk': '117d16c0b1c9397a0573c28b67dad6f8'})
print(response2.text)
"""
get xxxxxx
"""
在CBV类中通过authentication_classes = [CustomAuthentication, ]
指定,比如上面例子中的做法。
并不是所有的api都需要作认证,比如登录。因此可以通过写一个基类(指定认证类),让需要认证的api首先继承这个基类即可:
# 基类
class Token_auth(APIView):
authentication_classes = [CustomAuthentication, ] # 指定认证方式
# 需要认证的api 首先继承基类
class TestView(Token_auth, APIView):
versioning_class = URLPathVersioning # 指定版本
pass
# 不需要认证的api, 不继承基类
class AuthView(APIView):
pass
在settings中作全局配置,不需要认证的api指定authentication_classes = []
为空即可
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
# 自定义认证类路径
'utils.authentication.CustomAuthentication',
]
}
class AuthView(APIView):
authentication_classes = []
遵循之前的生命周期分析,进入CBV视图后,流程如下:
dispatch(self, request, *args, **kwargs)
request = self.initialize_request(request, *args, **kwargs) 二次封装request
return Request(
# ...
authenticators=self.get_authenticators(), # 为request对象封装认证类
)
def get_authenticators(self):
return [auth() for auth in self.authentication_classes] # 循环认证类列表,并实例化对象
self.initial(request, *args, **kwargs) 初始化
self.perform_authentication(request) 执行认证
request.user 调用request对象user方法(@property装饰)(登录之后存在request.user 同django默认设计)
self._authenticate()
def _authenticate(self):
"""
执行每个认证对象的认证方法:
一旦异常raise 全部终止,交由dispatch中的异常处理
如果返回元组,赋值给request.user, request.auth, 并return 后续不再执行
如果既没有异常,又没有返回,执行_not_authenticated() 匿名用户
"""
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
执行自带认证类或自定义认证类中authenticate方法
class CustomAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get('tk')
token_obj = models.Token.objects.filter(token=token).first()
if token_obj:
# 返回(用户对象,token对象)
return (token_obj.user, token_obj)
# return None # 支持匿名用户,将执行 self._not_authenticated()
raise exceptions.AuthenticationFailed('认证失败') # 不允许匿名用户,交给dispatch中的异常处理
匿名用户
def _not_authenticated(self):
"""
为未认证的请求设置authenticator, user & authtoken
默认值分别是 None, AnonymousUser & None,后两个可以在settings中配置
"""
self._authenticator = None
if api_settings.UNAUTHENTICATED_USER: # 默认配置中会使用django内置的AnonymousUser类
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
# 匿名用户settings相关配置
REST_FRAMEWORK = {
'UNAUTHENTICATED_USER': None, # 取消匿名用户
'UNAUTHENTICATED_TOKEN': None,
}
如果认证类的authenticate
方法执行了returen None
,导致user_auth_tuple
为空,进而执行self._not_authenticated()
方法时,将默认产生一个匿名用户。那么request.user
非空,而是一个匿名用户对象。如果希望取消对匿名用户的支持,就需要在settings中指定'UNAUTHENTICATED_USER': None
,来覆盖默认的匿名用户配置。
分析了上面的认证后,权限的流程是一摸一样的,下面我们看一下具体用法
class CustomPermission(BasePermission):
message = '无权限' # 查看源码可知,可以通过message自定义提示信息
def has_permission(self, request, view):
"""返回True: 有权限;返回False: 无权限"""
method = request._request.method
if request.user.user_type == 1 and isinstance(view, TestView) and method == 'POST': # 限制普通用户通过post方式访问TestView
return False
return True
# 方式一:局部视图
class TestView(Token_auth, APIView):
versioning_class = URLPathVersioning # 指定版本
permission_classes = [CustomPermission, ] # 指定权限
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
print(request.user.username)
print(request.user.email)
return Response('get xxxxxx')
def post(self, request, *args, **kwargs):
return Response('post xxx')
# 方式二:settings全局
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
# path.to.Permissionclass 路径之间用点分割
]
}
这样当我们通过get方法访问TestView时,将得到正常响应,如果是post,那么将得到{"detail":"无权限"}
的响应。
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_RATES": {
'anon': '5/m', # scope: rate 匿名用户: 每分钟5次
'user': '10/m' # 登录用户
}
}
from rest_framework.throttling import SimpleRateThrottle
# 根据request.user 判断匿名不匿名 (在每次进来时认证中赋值了用户或None)
class Custom_anno_control(SimpleRateThrottle):
"""匿名用户控制,用默认get_ident,获取ip作为标识"""
scope = 'anon' # 决定settings中DEFAULT_THROTTLE_RATES 的key
def allow_request(self, request, view):
if request.user: # 如果是登录用户,不限制
return True
self.key = self.get_cache_key(request, view)
print('key=====',self.key)
self.history = self.cache.get(self.key, [])
self.now = self.timer()
while self.history and self.history[-1] <= self.now - self.duration:
self.history.pop()
if len(self.history) >= self.num_requests:
return self.throttle_failure()
return self.throttle_success()
def get_cache_key(self, request, view):
return self.cache_format % {
'scope': self.scope,
'ident': self.get_ident(request)
}
class Custom_user_control(SimpleRateThrottle):
"""登录用户控制,直接用用户名+CBV视图类名作为标识"""
scope = 'user'
def allow_request(self, request, view):
if not request.user: # 如果是匿名用户,不限制
return True
self.key = request.user.username + view.__class__.__name__ # 如果登录用户,用用户名和类名作为标识
if self.key is None:
return True
self.history = self.cache.get(self.key, [])
self.now = self.timer()
while self.history and self.history[-1] <= self.now - self.duration:
self.history.pop()
if len(self.history) >= self.num_requests:
return self.throttle_failure()
return self.throttle_success()
这里参考源码稍作修改:
class CustomPermission(BasePermission):
message = '无权限' # 查看源码可知,可以通过message自定义提示信息
def has_permission(self, request, view):
"""返回True: 有权限;返回False: 无权限"""
if not request.user: # 仅允许登录用户,限制匿名用户
return False
return True
class IndexView(APIView):
"""控制登录用户访问频次:10/m, 匿名用户访问频次5/m"""
authentication_classes = [CustomAuthentication, ] # 获取登录token和用户;如果不认证限制,那么无法自动获取token,都是匿名访问
throttle_classes = [Custom_anno_control, Custom_user_control]
# 要同时允许登录用户和匿名用户的访问并作限制,必须同时指定authentication_classes认证类和throttle_classes访问控制类:
# 如果用户登录,那么拿着token,可以访问配置中指定的次数 'user': '10/m'
# 如果用户未登录,那么没有token或者token错误,可以访问配置中指定的数 'anon': '5/m',
def get(self, request, *args, **kwargs):
return HttpResponse('欢迎访问首页')
class ShoppingView(APIView):
versioning_class = URLPathVersioning # 指定版本
authentication_classes = [CustomAuthentication, ] # 认证(认证可能同时允许登录用户和匿名用户)
permission_classes = [CustomPermission, ] # 指定权限,这里作二次限制,即便匿名用户通过了认证,也过不了权限
throttle_classes = [Custom_user_control, ] # 限制登录用户的访问次数
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
print(request.user.username)
print(request.user.email)
return HttpResponse('购物车 访问')
def post(self, request, *args, **kwargs):
return HttpResponse('购物车 提交')
根据请求头中的content-type,对内容进行解析。在执行request.data时触发。
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser', # content-type: application/json
'rest_framework.parsers.FormParser', # content-type: application/x-www-form-urlencoded
'rest_framework.parsers.MultiPartParser' # content-type: multipart/form-data(可以在form中同时上传数据和文件)
),
默认同时支持以上三种解析器(源码中通过for 循环一一匹配请求头的content-type),还有一个FileUploadParser (只能上传文件,鸡肋)。如果想配置的话可以在CBV中指定parser_classes=[]
,或者在配置中配置,没啥必要,默认都配置上了,除非你闲的蛋疼。。。
对于序列化,有两种方案,一种是将查询结果通过.value_list('field1', 'field2','xxx')
这种方式,返回QuerySet包字典的格式,然后转化为列表:
queryset = models.UserInfo.objects.all().values_list('id', 'name')
res = list(queryset)
这种方式有个弊端,无法处理choice,M2M字段的情况。
第二种方案就是这里的序列化类,REST中内置了三种:
from rest_framework.response import Response # 对于序列化的结果需要用Response对象才能正确返回结果
from rest_framework import serializers
from rest_framework.serializers import Serializer
from rest_framework.serializers import ModelSerializer
from rest_framework.serializers import HyperlinkedModelSerializer
# 派生Serializer类
# 两种方式:Serializer 和 ModelSerializer (相当与Django中的Form和ModelForm)
class UserSerializer(Serializer):
username = serializers.CharField()
password = serializers.CharField()
email = serializers.EmailField()
user_type = serializers.IntegerField()
group = serializers.CharField(source="group.title", required=False) # 通过source指定FK对象的显示
class UserModelSerializer(ModelSerializer):
class Meta:
model = models.UserInfo
fields = '__all__'
depth = 2
# depth = 0 或 1, 只显示FK的PK, 如果=2,可以显示FK对象的字段,比如下面的group外键;
# [{"id":1,"username":"Lena",..."group":{"id":1,"title":"A组"}},
# 如果外键嵌套很多,depth深度过深可能会影响性能。。
# 返回json数据
class SerializerView(APIView):
def get(self, request, *args, **kwargs):
user_list = models.UserInfo.objects.all()
user_obj = models.UserInfo.objects.all().first()
ser = UserSerializer(instance=user_list, many=True) # 返回queryset序列化对象时,many=True
# ser = UserModelSerializer(instance=user_list, many=True)
# ser = UserSerializer(instance=user_obj, many=False) # 返回单个序列化对象时,many=False
return Response(ser.data)
对于choice字段,外键或者多对多等跨表字段,需要自定制
from rest_framework import serializers
# 假设CourseDetail 和 Course 表是一对一关系
class CourseDetailSerializer(serializers.ModelSerializer):
"""课程详情"""
course_name = serializers.CharField(source='course.name') # O2O跨表
recommend_courses = serializers.SerializerMethodField() # 写一个函数 def get_field(self, obj),返回的结果就是该字段的结果
price_policy = serializers.SerializerMethodField()
class Meta:
model = models.CourseDetail
fields = ['id', 'course_name', 'recommend_courses']
def get_recommend_courses(self, obj): # obj指当前表CourseDetail中的一条记录
"""获取M2M字段的结果"""
ret = []
recommend_courses_list = obj.recommend_courses.all()
for item in recommend_courses_list:
ret.append({'id': item.id, 'name': item.name})
return ret
def get_price_policy(self, obj):
"""获取choice字段的结果"""
ret = []
price_policy = obj.course.price_policy.all()
for item in price_policy:
ret.append({'valid_period': item.get_valid_period_display(), 'price': item.price})
return ret
# 也可以继承派生字段类型,只需要重写get_attribute 和 to_representation 方法即可
class MtoMField(serializers.CharField):
def get_attribute(self, instance):
return instance.objects.values('name','title')
def to_representation(self,value):
return list(value)
class MyField(serializers.CharField):
def get_attribute(self, instance):
#instance 是数据库对应的每行数据,即model 实例对象
data_list = instance.recommend_courses.all()
return data_list
def to_representation(self, value):
ret = []
for row in value:
ret.append({'id': row.id, 'name': row.name})
return ret
如果希望序列化的结果包括相关的链接关系,那么需要在序列化对象时提供当前的request,这里只需要提供一个上下文参数context即可实现。注意路由需要传id
urlpatterns = [
url(r'test/(?P\d+)/' , views.TestView.as_view(), name='test'),
]
class ModelUserSerializer(serializers.ModelSerializer):
ut = serializers.HyperlinkedIdentityField(view_name='test')
class Meta:
model = models.UserInfo
fields = "__all__"
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})
return Response(ser.data)
from rest_framework import serializers
from rest_framework.response import Response
class UserSerialize(serializers.HyperlinkedModelSerializer):
class Meta:
model = models.UserInfo
fields = ['user','pwd','id','url']
class SerializeView(APIView):
def get(self, request, *args, **kwargs):
user_list = models.UserInfo.objects.all()
ser = UserSerialize(instance=user_list,many=True,context={'request': request})
return Response(ser.data)
def post(self, request, *args, **kwargs):
ser = UserSerialize(data=request.data)
if ser.is_valid():
print(ser.validated_data)
print(request.data)
return Response(ser.validated_data)
else:
return Response(ser.errors)
# 自定义验证类, 在__call__中写验证逻辑
class PasswordValidator:
def __init__(self):
pass
def __call__(self, value):
if value != '123':
raise serializers.ValidationError('密码必须是123')
def set_context(self, serializer_field):
pass
# 派生Serializer,同样有两种方式
# 两种方式:Serializer 和 ModelSerializer (相当与Django中的Form和ModelForm)
class UserSerializer(Serializer):
username = serializers.CharField(min_length=6)
password = serializers.CharField(validators=[PasswordValidator(),])
email = serializers.EmailField()
user_type = serializers.IntegerField()
group = serializers.CharField(source="group.title", required=False) # 通过source指定FK对象的显示
class UserModelSerializer(ModelSerializer):
# username = serializers.CharField(min_length=6) 相当于下面的extra_kwargs
# password = serializers.CharField(validators=[PasswordValidator(), ])
class Meta:
model = models.UserInfo
fields = '__all__'
extra_kwargs = {
'username': {'min_length': 6},
'password': {'validators: [PasswordValidator(),]'}
}
class SerializerView(APIView):
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('got post .....')
post提交数据
api = 'http://127.0.0.1:8899/api/v1/ser/'
response = requests.post(url=api, data={'username':'sebastian', 'password':123, 'email':'asb'})
通过requests模块模拟post提交,CBV中打印结果如下:
{'email': ['Enter a valid email address.'], 'user_type': ['This field is required.']}
分页器类不能直接使用,需要继承它并指定参数
urlpatterns = [
url(r'api/page/$', views.PageTestView.as_view())
]
from rest_framework.pagination import PageNumberPagination
from rest_framework import serializers
class CustomPagination(PageNumberPagination):
# http://api.example.org/accounts/?page=4&page_size=100
# 指定客户端query_param参数:每页数据大小 和 页码
page_size_query_param = 'page_size'
page_query_param = 'page'
# 定制每页显示多少条数据(默认为None, 最终取决于请求中的查询参数) 以及最大值
page_size = 10
max_page_size = 20
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = "__all__"
class PageTestView(APIView):
def get(self, request, *args, **kwargs):
user_list = models.UserInfo.objects.all()
# 实例化分页对象,并根据请求参数,获取分页数据
paginator = CustomPagination()
page_user_list = paginator.paginate_queryset(user_list, request, view=self)
# 序列化分页数据
ser = UserSerializer(instance=page_user_list, many=True)
# 获取分页响应(可额外生成上一页/下一页链接)
response = paginator.get_paginated_response(ser.data)
return response
可能会报如下警告,对于无序的数据,分页器生成的分页数据可能不一致:
UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list
因此在获取数据库数据时,可以做一下排序,这样就不会报警告了:
user_list = models.UserInfo.objects.all().order_by('id')
通过requests模块对该api发起请求,将得到如下结果:
{"count":2,"next":"http://127.0.0.1:8899/api/page/?page=2&page_size=1","previous":null,"results":[{"id":1,"username":"Lena","password":"123","email":"[email protected]","user_type":1,"group":1}]}
class CustomPagination(LimitOffsetPagination):
# http://api.example.org/accounts/?offset=400&limit=100
limit_query_param = 'limit'
offset_query_param = 'offset'
max_limit = None
default_limit = 10
对于以上两种分页方式,都存在性能问题,页码往后翻的越多,速度越慢。即便是offset,每次也要从头扫描,因此如果每次都能从上一次索引位置继续的话,就可以解决性能下降的问题。看下面的几种情况:
select * from tb where dept = 'it'
select * from tb where dept = 'it' limit 1 # 性能高
加入limit,找到1条就不找了,否则找完整个表,速度自然慢。
select * from tb offset 0 limit 5
select * from tb offset 100 limit 5
select * from tb offset 1000 limit 5
...
select * from tb where id>1000 offset 0 limit 5 # 性能高
通过id筛选,跳过前面的,这样就不用从头扫描。这就是cursor游标分页的原理。cursor分页每一次从上一次索引位置继续,因此只能上一页,下一页,不能直接跳转页码。
class CustomPagination(CursorPagination):
# URL传入的游标参数
cursor_query_param = 'cursor'
# 默认每页显示的数据条数
page_size = 1
# URL传入的每页显示条数的参数
page_size_query_param = 'page_size'
# 每页显示数据最大条数
max_page_size = 1000
# 根据ID从大到小排列
ordering = "id"
通过requests模块访问,结果如下
api = 'http://127.0.0.1:8899/api/page/?page_size=1'
response = requests.get(url=api)
print(response.text)
"""
{"next":"http://127.0.0.1:8899/api/page/?cursor=cD0x&page_size=1","previous":null,"results":[{"id":1,"username":"Lena","password":"123","email":"[email protected]","user_type":1,"group":1}]}
"""
可以看到cursor=cD0x
是加密,看不到第几页,只能一页页翻,没办法通过指定cursor的值直接翻页。
如果要支持url带后缀,比如.json
,那么可以在路由规则后面加\.(?P
(具体见后面的渲染器部分)。下面通过实现增删改查视图,来看看几种不同路由方式的区别。
增删改查分别对应几种不同请求方法:
需要写两套路由,以分别支持无id和有id传参的情况,每套路由还要支持无url后缀和有url后缀的情况,共计4条路由。推荐手动路由,可定制性强。
urlpatterns = [
# http: //127.0.0.1:8000/api/router 无id
# GET: 查询(列表)
# POST: 增加
url(r'api/router/$', views.RouterView.as_view()),
url(r'api/router\.(?P\w+)$' , views.RouterView.as_view()), # 支持后缀
# http: //127.0.0.1:8000/api/router/1 有id
# GET: 查询(单条记录)
# PUT: 更新
# DELETE: 删除
url(r'api/router/(?P\d+)/$' , views.RouterView.as_view()),
url(r'api/router/(?P\d+)\.(?P\w+)$' , views.RouterView.as_view()), # 支持后缀
]
视图中手动实现这几种请求方式:
from rest_framework.serializers import ModelSerializer
from rest_framework.views import APIView
class RouterSerializer(ModelSerializer):
class Meta:
model = models.UserInfo
fields = '__all__'
class RouterView(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
obj = models.UserInfo.objects.filter(pk=pk).first()
ser = RouterSerializer(instance=obj, many=False)
else:
user_list = models.UserInfo.objects.all()
ser = RouterSerializer(instance=user_list, many=True)
return Response(ser.data)
def post(self, request, *args, **kwargs):
pass
def put(self, request, *args, **kwargs):
pk = kwargs.get('pk')
pass
def delete(self, request, *args, **kwargs):
pk = kwargs.get('pk')
pass
视图继承中继承ModelViewSet
,其中提供了增删改查方法,不过需要在路由中指定。(视图部分存疑先)
urlpatterns = [
url(r'api/router/$', views.RouterView.as_view({'get': 'list', 'post': 'create'})),
url(r'api/router/(?P\d+)/$' , views.RouterView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
]
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
class RouterSerializer(ModelSerializer):
class Meta:
model = models.UserInfo
fields = '__all__'
class RouterView(ModelViewSet):
queryset = models.UserInfo.objects.all()
serializer_class = RouterSerializer
from django.conf.urls import url,include
from app01 import views
from rest_framework.routers import DefaultRouter # 自动路由
router = DefaultRouter() # 实例化router对象
router.register(r'/XXX/', views.TargetView1) # 将目标视图注册到router对象上
router.register(r'/XXY/', views.TargetView2) # 可以注册多个
urlpatterns = [
url(r'^', include(router.urls)), # 自动实现增删改路由
]
from rest_framework.viewsets import ModelViewSet
from rest_framework import serializers
class RouteSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = "__all__"
class RouteView(ModelViewSet):
queryset = models.UserInfo.objects.all()
serializer_class = RouteSerializer
根据 用户请求URL 或 用户可接受的类型,筛选出合适的 渲染组件。注意,如果要支持url后缀,路由正则后面必须加\.(?P
。如果同时多个存在时,自动根据URL后缀来选择渲染器。
urlpatterns = [
url(r'test/$', views.RenderTestView.as_view()),
url(r'test\.(?P\w+)$' , views.RenderTestView.as_view()), # 支持后缀
]
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 rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
from rest_framework import serializers
from rest_framework.renderers import JSONRenderer
class CustomPagination(PageNumberPagination):
pass
class RenderTestSerializer(serializers.ModelSerializer):
pass
class RenderTestView(APIView):
renderer_classes = [JSONRenderer, ]
def get(self, request, *args, **kwargs):
pass
以table表友好地呈现 ,好看,没多大用。
http://127.0.0.1:8000/test/?format=admin
http://127.0.0.1:8000/test.admin
http://127.0.0.1:8000/test/
from rest_framework.renderers import HTMLFormRenderer
class RenderTestView(APIView):
renderer_classes = [HTMLFormRenderer, ]
def get(self, request, *args, **kwargs):
pass
form表单,只能返回单个序列化对象,否则报错,没暖用。
http://127.0.0.1:8000/test/?format=admin
http://127.0.0.1:8000/test.admin
http://127.0.0.1:8000/test/
from rest_framework.renderers import HTMLFormRenderer
class RenderTestView(APIView):
renderer_classes = [HTMLFormRenderer, ]
def get(self, request, *args, **kwargs):
pass
这种是最常用的
http://127.0.0.1:8000/test/?format=api
http://127.0.0.1:8000/test.json
http://127.0.0.1:8000/test/
from rest_framework.renderers import JSONRenderer
from rest_framework.renderers import BrowsableAPIRenderer
class RenderTestView(APIView):
renderer_classes = [JSONRenderer, BrowsableAPIRenderer, ]
def get(self, request, *args, **kwargs):
pass