一. 什么是rest framework ?
RestFramework 是一个能快速为我们提供遵循 restful规范的API接口,实现前后端分离, 后端人员只需要提供api(通常是url)给前端, 让前端通过api获取数据进行处理,渲染 展示在页面上
二. restful API 设计规范
1. 通信协议, 通常是 https 协议 (http也可以)域名
2. 域名
方式一: api.example.com API部署在专用域名上(这么做会存在跨域问题)
方式二: example.org/api/ 写在路径上,API很简单 (常用)
3. 版本
方式一: 写在URL上 例如: https://example.org/api/v1/
方式二: 写在请求头里 (跨域时会触发多次请求) 常用)
4. 路径,视网络上任何东西都是资源,均使用名词表示(可复数)
- https://api.example.com/v1/zoos
- https://api.example.com/v1/animals
- https://api.example.com/v1/employees
5. method 请求方式
后台根据不同的请求方式, 做不同的处理, 常用的请求方式及操作如下:
- GET :从服务器取出资源(一项或多项)
- POST :在服务器新建一个资源
- PUT :在服务器更新资源(客户端提供改变后的完整资源)
- PATCH :在服务器更新资源(客户端提供改变的属性)
- DELETE :从服务器删除资源
6. 过滤
- https://api.example.com/v1/zoos?limit=10: 指定返回记录的数量
- https://api.example.com/v1/zoos?offset=10: 指定返回记录的开始位置
- https://api.example.com/v1/zoos?page=2&per_page=100: 指定第几页,以及每页的记录数
- https://api.example.com/v1/zoos?sortby=name&order=asc: 指定返回结果按照哪个属性排序,以及排序顺序
- https://api.example.com/v1/zoos?animal_type_id=1: 指定筛选条件
7. 状态码
根据接口返回的状态码来判断请求的状态, 比如4XX的状态码代表出错等, 常见的状态码如下表:
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 - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
其他的状态码可以看这里: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
8. 错误处理 状态码为4XX的时候, 应当返回错误信息, 以error当key
{
error: "Invalid API key"
}
9. 返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范。
GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档
10. Hypermedia API
RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么
{"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos",
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}}
以上资料借鉴于: www.cnblogs.com/wupeiqi/art…
三. Django 中使用rest framework
1. 安装rest framework
pip install djangorestframework
2. 认证规则
- 源码中认证的流程
所有的请求进来 都是先走 APIView 中的dispatch() 函数, 如果视图中自定义了dispatch就使用自定义的。
# dispatch中的initialize_request方法对原生request进行封装,
request = self.initialize_request(request, *args, **kwargs)
# initialize_request中封装原生request时 用self.get_authenticators() 去拿到所有的认证对象并封装给request
authenticators=self.get_authenticators()
# get_authenticators()中去配置文件中拿配置的认证类
return [auth() for auth in self.authentication_classes]
# 默认配置
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
########## 将认证对象封装给request后, 执行 dispatch 中的 initial 方法 ###########
self.initial(request, *args, **kwargs)
# initial 中的perform_authentication 方法获取认证的返回结果
self.perform_authentication(request)
#self.perform_authentication(request) 方法 中返回 request.user
# 认证的处理方法时在Request.user中处理
####### Request.user #######
request.user 中执行 self._authenticate()
####### _authenticate 源码 ########
def _authenticate(self):
# 遍历所有的认证对象, 因为认证对象可以有多个
for authenticator in self.authenticators:
try:
# 执行每个认证对象的 authenticate() 方法,并将返回值赋值给 user_auth_tuple元组
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
# 认证对象中抛出异常时 执行 _not_authenticated() 并抛出异常
self._not_authenticated()
raise
# 认证对象有返回值 且 不为None时 将元组中的值分别复制给 self.user 和 self.auth
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return
# 认证对象有返回值 但返回值为None时 执行 _not_authenticated()
self._not_authenticated()
####### _not_authenticated 源码 ########
def _not_authenticated(self):
self._authenticator = None
# 去配置文件中拿 self.user 默认配置值 如果没配置则为None
if api_settings.UNAUTHENTICATED_USER:
self.user = api_settings.UNAUTHENTICATED_USER()
else:
self.user = None
# 去配置文件中拿 self.auth 默认配置值 如果没配置则为None
if api_settings.UNAUTHENTICATED_TOKEN:
self.auth = api_settings.UNAUTHENTICATED_TOKEN()
else:
self.auth = None
- rest framework 内置的认证类
# 我们自定义认证类时最好 继承这个基类
from rest_framework.authentication import BaseAuthentication
- 自定义认证类
自定义认证类 最好继承 BaseAuthentication 这个内置认证类 并实现此类的 authenticate 和 authenticate_header 方法
from rest_framework.authentication import BaseAuthentication
class MyAuth(BaseAuthentication):
def authenticate(self,request):
pass # 这里写具体的认证规则 比如 拿到 token 比对 数据库
def authenticate_header(self,request):
pass # 这里默认pass就行
# 一个简单认证类例子
def authenticate(self, request):
ret = {"code":200,"msg":None}
token = request._request.GET.get("token")
if not token:
ret["code"] = 400
ret["msg"] = "用户认证失败"
raise exceptions.AuthenticationFailed(ret)
m = UserToken.objects.filter(token=token).first()
if not m:
ret["code"] = 400
ret["msg"] = "用户认证失败"
raise exceptions.AuthenticationFailed(ret)
return (m.user,m)
认证类的返回值有三种
None #通过当前认证交给下一个认证类
exceptions.AuthenticationFailed('用户认证失败') #用户认证失败
(元素1,元素2) #元素1交给request.user, 元素2交给request.auth
全局配置
在项目的settings.py 配置文件中加入下边的配置
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [Authtication的path,] #可以新建一个包,把自定义的认证类放至包,在此列表加入此认证类的全路径即可,可以加入多个认证类
#例如:
"DEFAULT_AUTHENTICATION_CLASSES": ['quickstart.utile.auth.Authtication',] #Authtication就是我们自定义的认证类
}
配置全局配置后 如果视图中的某个视图 比如登录视图 不需要此认证规则, 在该视图中加入下行:
classs LoginView(APIView):
authentication_classes = []
......
匿名用户配置, 即认证类返回None
REST_FRAMEWORK = {
# "UNAUTHENTICATED_USER":lambda:"匿名用户", # 自定义匿名用户名
"UNAUTHENTICATED_USER":None, #推荐为None
"UNAUTHENTICATED_TOKEN":None,
}
2. 权限管理
- 源码中权限管理流程
权限管理流程跟认证流程类似, 只是走 initial 方法中的 self.check_permissions(request) 去获取权限管理类对象
- rest framework 内置的权限类
from rest_framework.permissions import BasePermission
- 自定义权限管理类
跟自定义认证类一样, 自定义权限类 也最好继承 BasePermission类 实现 has_permission方法
from rest_framework.permissions import BasePermission
class SvipPermission(BasePermission):
def has_permission(self, request, view):
if request.user.user_type != 3: # 这里做权限判断
return False
return True
权限类的返回值 只有 两种
True # 返回True 代表有权限
False # 返回False 代表没有权限
全局配置
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [Permission的path,] #可以新建一个包,把自定义的权限类放至包,在此列表加入此认证类的全路径即可,可以加入多个权限类
#例如:
"DEFAULT_PERMISSION_CLASSES":['quickstart.utile.premission.SvipPermission',] #SvipPermission就是我们自定义的权限类
}
配置全局配置后 如果视图中的某个视图 不需要此权限规则, 在该视图中加入下行:
authentication_classes = []
3. 访问频率控制 (节流)
- 内置访问频率类
from rest_framework.throttling import SimpleRateThrottle # 常用
from rest_framework.throttling import BaseThrottle
- 自定义访问频率控制类
自定义访问频率一般继承 SimpleRateThrottle 实现 get_cache_key() 方法
from rest_framework.throttling import SimpleRateThrottle
class User_thrott(SimpleRateThrottle):
scope = "mm_user" #配置文件内获取 配置节流数据的key
def get_cache_key(self,request,view):
return request.user.username #通过用户名来节流
class Addr_thrott(SimpleRateThrottle): #继承内置的节流类
scope = "addr_" #配置文件内获取 配置节流数据的key
def get_cache_key(self,request,view):
return self.get_ident(request) #通过访问ip节流
全局配置
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": ['quickstart.utile.thrott.User_thrott'], #自定义节流类的path
"DEFAULT_THROTTLE_RATES":{
'mm_user':'10/m', # {'s': 1秒, 'm': 60秒, 'h': 3600秒, 'd': 86400秒} 10=> 次数
'addr_':'3/m', #此处的 addr为自定义节流类中指定的key
}
部分视图不使用此规则 可在视图中加入:
throttle_classes = []
一个完整的认证,权限,节流配置示例:
REST_FRAMEWORK = {
# 认证类配置
"DEFAULT_AUTHENTICATION_CLASSES":["xuesheng.utiles.authtucate.MyAuth",],
# 权限类配置
"DEFAULT_PERMISSION_CLASSES":["xuesheng.utiles.permi.MyPer",],
# 匿名用户配置, 即认证时不作任何处理
"UNAUTHENTICATED_USER":lambda:"匿名用户",
"UNAUTHENTICATED_TOKEN":None,
# 节流类配置
"DEFAULT_THROTTLE_CLASSES":["xuesheng.utiles.jieliu.YmJiuliu",],
# 节流中去拿频率配置的key
"DEFAULT_THROTTLE_RATES":{
"addr_":"10/m",
"user_":"20/m",
}
}
4. 版本控制
- URL中通过get传版本参数(很少使用)
#settings.py 中配置
REST_FRAMEWORK = {
'DEFAULT_VERSION':'v1', #默认版本号
'ALLOWED_VERSIONS':['v1','v2','v3'], #允许的版本号
'VERSION_PARAM':'version', #获取版本的key
}
#views.py 中使用
from rest_framework.versioning import QueryParameterVersioning #内置版本模块
class UserView(APIView):
versioning_class = QueryParameterVersioning #使用,将version 储存在request中
def get(self,request,*args, **kwargs):
print(request.version) #通过request.version 来获取版本号
return ...
- 在URL路径中获取版本参数(经常使用)
#settings.py 中配置
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning', #全局配置
'DEFAULT_VERSION':'v1', #默认版本号
'ALLOWED_VERSIONS':['v1','v2','v3'], #允许的版本号
'VERSION_PARAM':'version', #获取版本的key
}
#urls.py 中需要添加获取版本的正则
re_path(r'^(?P<version>[v1|v2][v3]+)/user/$') #只允许v1,v2,v3 可以自定义
#views中使用
class UserView(APIView):
def get(self,request,*args, **kwargs):
print(request.version) #直接通过request.version 来获取版本号
print(request.versioning_scheme) #获取处理版本的对象
ul = request.versioning_scheme.reverse(viewname='uuu',request=request) #获取反省生成的url
return ...
5. 解析器
- django从请求中拿数据的两个地方:
- request.body
- request.POSTs
要从request.POST中拿到请求提交的数据必须满足两个条件:
Content-Type: application/x-www-form-urlencodeed 和 数据格式
ps: 如果请求头中的 Content-Type:application/x-www-form-urlencodeed, request.POST中才有值,(去request.body中解析数据)
数据格式类似于 name=user&age=24&gender=男
- rest framework 全局配置解析器
#settings.py文件新增
REST_FRAMEWORK = {
'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 头 上传文件
'rest_framework.parsers.FileUploadParser' #可解析Content-Type: */* 头
)
}
如果某个类视图需要单独使用某种解析器, 可在该视图中加入下列配置:
from rest_framework.parsers import JSONParser,FormParser,MultiPartParser,FileUploadParse # 先导入解析器
class ParserView(APIView):
parser_classes = (FileUploadParse,) #需要接收哪些格式就加入哪些
request.data #获取请求中提交的数据
...
6. 序列化
序列化的两个主要作用:
- 对用户请求提交的数据进行验证
- 将数据库的数据序列化之后返回给用户
- 序列化数据
1. 自定义序列化类有两种方式, 一种是继承serializers.Serializer,另一种是继承 serializers.ModelSerializer
# 继承serializers.Serializer时 需要自己写字段
from rest_framework import serializers
class UserInfoSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
#继承serializers.ModelSerializer 时,可以使用自己写的字段,也可以在fields中添加上需要的字段名,也可以
class UserInfoSerializer(serializers.ModelSerializer):
user_type = serializers.CharField(source="get_user_type_display") #source => 数据源
gp = serializers.CharField(source="group.title")
rls = serializers.SerializerMethodField() #自定义显示
class Meta:
model = User
# fields = "__all__" #查询所有字段
fields = ['id','username','password','user_type','gp','rls'] #user_type和gp为自定义字段, rls为自定义显示类, 可混合使用
def get_rls(self,row): #自定义方法 方法名以 get_ 开头 字段名结尾, 只有一个参数row(即当前行)
role_obj_list = row.roles.all()
ret = []
for item in role_obj_list:
ret.append({'id:item.id,'title':item.title})
return ret
2. 自动序列化表(深度控制)
depth = num
num=> 自动序列化连表的层数 (最好不要超过10,建议0-3之间)
一个简单全面的序列化类例子:
class UserInfoSerializer(serializers.ModelSerializer):
class Meta:
fields = "__all__" # 所有的字段都要
depth = 1 # 自动序列化一层
3. 生成链接
这里以获取user信息中返回用户组的链接为例:
class UserInfoSerializer2(serializers.ModelSerializer):
# HyperlinkedIdentityField反向生成连接
group = serializers.HyperlinkedIdentityField(view_name='group',lookup_url_kwarg='id_',lookup_field='group_id')
#view_name => url的name, lookup_url_kwarg = > url中获取分组名, lookup_field => 值
class Meta:
model = UserInfo
fields = [.... 'group' ]
class UserInfoViews(APIView):
def get(self, request,*args, **kwargs):
user = UserInfo.objects.all()
ser = UserInfoSerializer2(instance=user,many=True, context={"request":request})
#instance => queryset对象, many => 单挑数据是为False,多条数据为True 视图函数中实例化时需要加上context={'request': request}属性
ret = json.dumps(ser.data, ensure_ascii=False)
return HttpResponse(ret)
# 序列划分组类
class GroupSerializer(serializers.ModelSerializer):
class Meta():
model = UserGroup
fields = "__all__"
class GetGroupView(APIView):
def get(self, request,*args, **kwargs):
pk = kwargs.get("id_")
group = UserGroup.objects.filter(pk=pk).first()
ser = GroupSerializer(instance=group,many=False)
data = json.dumps(ser.data,ensure_ascii=False)
return HttpResponse(data)
#urls.py中
re_path(r'^(?P<version>[v1|v2]+)/group/(?P<id_>\d+)$',views.GetGroup.as_view(),name='group')
- 验证用户提交的数据
写验证类 基于ModelSerializer
class OrderSerializer(serializers.ModelSerializer):
print("hello")
class Meta():
model = Delivery
fields = "__all__"
# id = serializers.IntegerField(required=False)
open_code = serializers.CharField(min_length=6,max_length=6,required=True,error_messages={'max_length':'开箱码必须为6位','min_length':'开箱码必须为6位','required':'开箱码必须填写'})
def validate_open_code(self,value): #针对某个字段的钩子函数
if value.isdigit() or value.isalpha():
raise serializers.ValidationError("开箱码必须为数字和字母的组合")
else:
return value
def validate(self, value): # 全局钩子函数
order_id = value.get('order_id')
open_code = value.get('open_code')
if order_id and open_code:
return value # 满足条件时返回value
else:
raise serializers.ValidationError('异常') # 不满足时抛出异常
# 视图中使用
class DeliverysssView(APIView):
def post(self, request, *args, **kwargs):
ret = {"code":200,"status":"添加成功"}
ser = OrderSerializer(data=request.data)
if ser.is_valid(): # 验证成功之后做的操作, 比如写入数据库等
print(ser.data)
ret["order"] = ser.data
print("数据已添加")
else:
ret["code"] = 400
ret["status"] = "添加订单失败"
ret["msg"] = ser.errors
print(ser.errors)
return Response(ret)
7. 分页
- 看第N页, 每页显示多少条数据
使用的类:
from rest_framework.pagination import PageNumberPagination
自定义类:
class MyPageNumberPagination(PageNumberPagination):
page_size = 5 #每一页的数量
page_query_param = 'page' #自定义查询页的key
page_size_query_param = 'size' #在请求时自定义每个显示数量的key
max_page_size = 10 #最大显示数
views中使用:
class PageViews(APIView):
def get(self, request, *args, **kwargs):
orders = Order.objects.all() #从DB中获取数据
pages_order = MyPageNumberPagination() #获取分页
pg = pages_order.paginate_queryset(queryset=orders, request=request,view=self) #获取数据(queryset对象)
res = PageSerilizer(instance=pg, many=True) #序列化
return pages_order.get_paginated_response(res.data) #次方法返回会包含 下一页和上一页的url和查询的总数
如果使用原生类,需要在settings.py 配置文件中加一个'PAGE_SIZE':10
- 分页, 在第N个位置,向后查看N条数据
使用的类:
from rest_framework.pagination import LimitOffsetPagination
自定义类:
class MyLimitOffsetPagination(LimitOffsetPagination):
default_limit = 5 #每页的数量
limit_query_param = 'limit' #url中自定义查询数量的key
offset_query_param = 'offset' #查询起始id数
max_limit = 10 #最大限制数
#例如 http://127.0.0.1:8000/api/v2/orders/?limit=7&offset=3 从第三个数据往后查7条记录
views中使用同上, 只需要更改获取分页的类名
- 加密分页(CursorPagination), 上一页和下一页 (记住当前查询得最大id和最小id)
使用的类:
from rest_framework.pagination import CursorPagination
自定义类:
class MyCursorPagination(CursorPagination):
cursor_query_param = 'cursor' #页码的key
page_size = 4 #每页的数量
ordering = 'id' #按照字段排序 前加'-' 为倒序
page_size_query_param = 'size' #自定义数量的key
max_page_size = 10 #最大数量
views 中使用同1, 只需要更改获取分页的类名
8. 渲染器
配置文件中INSTALLED_APPS 中添加 'rest-framework' 这个app
要在配置文件中添加上需要的渲染器, 然后再url中添加规则, 就可以在页面上看到对应的渲染方式了
在views中返回的时候 用Response 返回数据
from rest_framework.response import Response
return Response(...)
# 配置文件
'DEFAULT_RENDERER_CLASSES':[
'rest_framework.renderers.JSONRenderer', # json 常用
'rest_framework.renderers.BrowsableAPIRenderer', # 页面 调试的时候可以方便的观看
'rest_framework.renderers.AdminRenderer', # 表格
]
# urls 例如
re_path(r'^(?P<version>[v1|v2]+)/delivery\.(?P<format>\w+)$',views.DeliveryView.as_view({'get':'list','post':'create'})),
# format 就是渲染器取渲染方式的key 访问方式如: 127.0.0.1:8000/api/v1/delivery.json 就是以json格式渲染
9. 视图
rest_framework 中有为我们写好了增 删 改 查 方法的视图,我们在使用的时候可以直接继承这个视图, 传入queryset对象, 序列化类, 和fenye类, 不需要自己再写內部的处理方法。 最全面的视图类为: ModelViewSet, 他继承了6个类
from rest_framework.viewsets import ModelViewSet
class ModelViewSet(mixins.CreateModelMixin, # post请求相关
mixins.RetrieveModelMixin, # get请求相关 查询单个数据 在url中需要传id
mixins.UpdateModelMixin, # put 和 patch 更新, 在url中需要传id
mixins.DestroyModelMixin, # delete参数相关 删除单个数据 在url中需要传id
mixins.ListModelMixin, # get 请求相关 查询所有数据
GenericViewSet) # 继承于
# 其中GenericViewSet又继承了两个类
from rest_framework.viewsets import GenericViewSet
class GenericViewSet(ViewSetMixin, # 为我们定义重构了as_view方法, 我们再url中需要添加不同方法对应的函数参数
generics.GenericAPIView)
# 自定义视图
class DeliveryViews(ModelViewSet): # 自定义类继承ModelViewSet
queryset = Order.objects.all() # queryset 指定model
serializer_class = PageSerilizer # 序列化使用的类
pagination_class = PageNumberPagination # 分页使用的类
# urls
re_path(r'^(?P<version>[v1|v2]+)/orders2/(?P<pk>\d+)$',views.ModelViews.as_view({'get':'retrieve','delete':'destroy','patch':'partial_update','put':'update'})), #url中传id 查询单挑数据或者更新 删除时使用
re_path(r'^(?P<version>[v1|v2]+)/orders2/$',views.ModelViews.as_view({'get':'list','post':'create'})), #不需要传id的url
使用建议:
如果你的类需要实现增删改查四种功能,就继承 ModelViewSet
如果只使用其中的部分功能, 比如增 查,可以只继承 对象的方法类 + GenericViewSet 例如下边只实现增删功能:
from rest_framework.mixins import CreateModelMixin,DestroyModelMixin
from rest_framework.viewsets import GenericViewSet
class MyView(CreateModelMixin,DestroyModelMixin,GenericViewSet):
pass
当然你完全可以不使用rest-framework 提供的视图, 负责逻辑 继承 GenericViewSet 或者 APIView 自己写对应的处理方法也是可以的
10. 路由系统
如果是我们自己写url的话, 一般每个url需要写4个, 写起来很麻烦, 我们可以使用rest-framework提供的路由器系统来自动生成这4条路由
使用示例:
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'delivery',views.ModelViews) #会自动生成四个路由
urlpatterns = [
re_path(r'^(?P<version>[v1|v2]+)/',include(router.urls)),
]
# 生成的四条路由分别是
re_path(r'^(?P<version>[v1|v2]+)/delivery/$',views.DeliverysssView.as_view({'get':'list','post':'create'})),
re_path(r'^(?P<version>[v1|v2]+)/delivery\.(?P<format>\w+)$',views.DeliverysssView.as_view({'get':'list','post':'create'})),
re_path(r'^(?P<version>[v1|v2]+)/delivery/(?P<pk>\d+)\.(?P<format>\w+)$',views.DeliverysssView.as_view({'get':'retrieve','delete':'destroy','put':'update'})),
re_path(r'^(?P<version>[v1|v2]+)/delivery/(?P<pk>\d+)$',views.DeliverysssView.as_view({'get':'retrieve','delete':'destroy','put':'update'})),
]
四. 添加一个Django的组件 ContentType
contenttypes是django内置的一个app,可以用来追踪所有app和model的对应关系, 并记录在ContentType表
中 (默认表名是: django_content_type)
假如现在有这样一个需求, 某个培训学校售卖基础课程, 和 进阶课程, 基础和进阶下边又有很多的小课程, 课程收费按照时间计算, 比如 民族舞1个月200, 3个月500, 半年800等, 我们需要设计一张表来记录所有的价格。 具体实现如下:
########## models ##########
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey,GenericRelation
class BaseClass(models.Model): # 基础课
name = models.CharField(max_length=20,null=False)
# 下边这行不会生成一个字段, 仅用来反向从关联的价格表找出某个课程的全部价格策略
price_list = GenericRelation('PricePolicy')
class VipClass(models.Model):
name = models.CharField(max_length=20, null=False)
# 下边这行不会生成一个字段, 仅用来反向从关联的价格表找出某个课程的全部价格策略
price_list = GenericRelation('PricePolicy')
class PricePolicy(models.Model):
"""
价格策略
"""
price = models.FloatField()
days = models.IntegerField()
# 这个字段记录对应的表名,这里我们跟ContentType做个ForeignKey, 用来从content_type表中获取对应表的id
content_type = models.ForeignKey(ContentType,verbose_name="关联的表名称",on_delete=models.CASCADE)
object_id = models.IntegerField(verbose_name="关联的课程ID")
# 下边这行不会生成新字段, 仅用于于课程表关联
content_object = GenericForeignKey('content_type','object_id')
########## views ###########
def add_price(request):
obj = BaseClass.objects.filter(name="民族舞").first()
# 先从课程表中查询到民族舞这个对象,直接传给价格表中的 content_object
PricePolicy.objects.create(price=9.9,days=30,content_object=obj)
def get_price(request):
obj = VipClass.objects.filter(id=2).first()
prices = obj.price_list.all()
# prices得到的结果就是从价格表中查到的ID为2课程的所有价格策略, 就不用我们再一个一个的去查