Django Rest-Framework

1,252 阅读12分钟

一. 什么是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从请求中拿数据的两个地方:
  1. request.body
  2. 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. 对用户请求提交的数据进行验证
  2. 将数据库的数据序列化之后返回给用户
  • 序列化数据

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课程的所有价格策略, 就不用我们再一个一个的去查