59 - Django Rest Framework

参考:

https://www.cnblogs.com/wupeiqi/articles/7805382.html

其他博文推荐

Django-rest framework框架

内容回顾&今日内容

内容回顾:
    1. git
        add 
        commit 
        push 
        
        pull 
        fetch
        merge 
        rebase
        
        stash 
        
        branch 
        checkout
        
        协同开发:
            - 每个人一个分支
            - review
            - bug分支
            - dev
            - master/release
        
        tag 做版本管理
        
        
    2. celery
        - 立即执行任务
        - 指定时间触发任务
        - 多任务管理目录结构
    
    今日内容相关的内容回顾
    3. 面向对象的封装
        
        request = 请求相关所有的数据
        
        
        class NewRequest(object):
            def __init__(self,req,parser,auth):
                self._request = req
                self.parser = parser
                self.auth = auth
                
        obj = NewRequest(request,'x1','x2')
        obj.parser
        obj.auth
        obj._request
        obj._request.POST
        
    4. 列表生成式
        v = [ i for i in range(10)]
        v = [0,1,2,3,...9]
    
        示例:
            class Auth1:
                pass 
            class Auth2:
                pass     
                
            class Foo(object):
                cls_list = [Auth1,Auth2]
                
                
                def get_cls_list_obj(self):
                    # return [Auth1(),Auth2() ]
                    return [ cl() for cl in self.cls_list]
                    
            obj = Foo()
            ret = obj.get_cls_list_obj()
            print(ret)
        
    5. 给你一个字符串 "utils.auth.Auth",帮我找到Auth类,并实例化。
        
    
    6. django中可以连接memcached/redis(安装组件)
    
        PS:访问频率


今日内容:
    - restful api
    - djangorestframework框架
        - 认证
        - 权限
        - 访问频率限制
        - 版本
        - 解析器
        - 序列化
            - 请求数据进行校验
            - 对queryset进行序列化
        - 分页
        - 路由
        - 视图
        - 渲染器    
        
        
内容详细:
    1. restful 
    
        - 什么是接口?
            URL:
                http://127.0.0.1:8000/index/
            约束类:
                
                Interface IFoo:
                    def send(self):pass 
                    
                
                class F1(IFoo):
                    def send(self):
                        pass 
                    
                    
                class F2(IFoo):
                    
                        
                
                    
                
                def msg(obj):
                    obj.send()
                    
                obj = F2()
                msg(obj)
        
        - 学生管理(CURD)
            原来的方式:
                url(r'^user_add/', views.user_add),
                url(r'^user_edit/', views.user_edit),
                url(r'^user_del/', views.user_del),
                url(r'^user_list/', views.user_list),
            现在的方式(遵循了restful规范的方式):
                url(r'^user/', views.user)
                    - GET,获取用户
                    - POST,增加用户
                    - PUT,修改
                    - DELETE,删除
                    
        - restful规范
            - http/https
            - 在URL上标识,是API:
                www.luffycity.com/api/  不存跨域
                api.luffycity.com         可能存在跨域
            
            - www.luffycity.com/api/salary
            
                面向资源编程,把网络上的任务东西都视为资源。
                
            - 版本:
                www.luffycity.com/api/v1/salary
                v1.luffycity.com/api/salary
            
            - 请求方法:
                - GET,获取用户
                - POST,增加用户
                - PUT,修改
                - DELETE,删除
                
            - 状态码
                - 
                - {code:1000,msg:'xxxx'}
            - 错误信息
                 {code:1000,msg:'xxxx'}
                 
            - 条件过滤
                https://www.example.com/v1/api/zoos?page=2&per_page=100
            
            - 返回结果:
            
            - Hypermedia API
                {
                    code:20000,
                    data:{
                        id:1,
                        name:'银秋良',
                        age:19,
                        # department_id:9
                        department_id: http://www.xxx.com/api/v1/department/9
                    }
                }
                                
                
        -> 以后再写接口,尽量遵循restful规范
        -> 以后再写程序,不遵循restful规范
                
        什么时候会写接口:
            - 前后端分离:
                前端:vue.js  react.js angular.js 
                后端:接口
            - App或智能设备:
                终端:app、玩具
                后端:接口
            
            忠告:看看vue.js 
                
    
    2. djangorestframework框架
        pip3 install django==1.11.7
        pip3 install djangorestframework 
        
        
        a. 基于django CBV实现API
        
        
        b.框架
            
            前夕:
                INSTALLED_APPS = [
                    'django.contrib.admin',
                    'django.contrib.auth',
                    'django.contrib.contenttypes',
                    'django.contrib.sessions',
                    'django.contrib.messages',
                    'django.contrib.staticfiles',
                    'app01.apps.App01Config',
                    'rest_framework',
                ]

            1. 版本 
                urlpatterns = [
                    url(r'^(?P<version>\w+)/user/', views.UserView.as_view()),
                ]

                
                class UserView(APIView):
                    # versioning_class = URLPathVersioning 优先级高于全局
                    
                    def get(self,request,*args,**kwargs):
                        print(request.version)
                        return HttpResponse('user.get')

                    def post(self,request,*args,**kwargs):
                        return HttpResponse('user.post')
                                
                REST_FRAMEWORK = {
                    "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",
                    'ALLOWED_VERSIONS':['v1','v2'],
                    "DEFAULT_VERSION":'v1',
                }
                
                
            2. 认证
                需求:为授权用户访问时,显示用户未登陆(token=asdfasdfadsfasdfasdf)
                     
                
            3. 权限 
                
                
            4. 用户访问频率控制、节流
                {
                    throttle_fengfeng: [1527322520.5065649, 1527322512.5065649,1527322509.5065649,],
                }
                
                
                
                第一步:去列表中pop已经失效
                        
                        1527322599.5065649 - 60 = 1527322539.5065649
                        
                        fengfeng:[]
                        
                第二步:计算个数:
                        fengfeng:[1527322599.5065649,]
                
                
                
            5. 序列化

                - 对QuerySet、对象进行序列化
                - 对用户请求数据进行校验
                
                
            6. 解析器
                - 根据请求头Content-Type的值,进行请求体数据的解析。
                
            
            7. 分页
                - 页码
                - limit offset
                - cursor
            
            
            8. 路由 
            
                    # 列表: SaView.get
                    # 添加:  SaView.post
                    url(r'^sa/$', views.SaView.as_view()),
                    # 详细 : SaView.get
                    # 修改 : SaView.put
                    # 局部 : SaView.patch
                    # 删除 : SaView.delete
                    url(r'^sa/(?P<pk>\d+)/$', views.SaView.as_view()),
                                
            9. 视图:
                    from django.views import View
                    from rest_framework.views import APIView            # 所有都没有
                    from rest_framework.generics import GenericAPIView  # 如果视图继承我以及我的子孙,路由中必须添加参数。
                    from rest_framework.viewsets import GenericViewSet
                    from rest_framework.viewsets import ModelViewSet     # 所有功能
                
            10. 渲染器
                

 

基于django CBV实现的API

目录结构

代码详细

settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',   # 添加app
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',    # 注释掉csrf,使用rest_framework的时候就不用注释掉这个了,会自动处理掉csrf的问题
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

m1day05/urls.py

from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    # url(r'^admin/', admin.site.urls),
    url(r'^user/', views.UserView.as_view()),
]

m1day05/views.py

from django.shortcuts import render,HttpResponse
from django.views import View

class UserView(View):

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

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

结果:

GET 访问 http://127.0.0.1:8000/user/ user.get

POST访问 http://127.0.0.1:8000/user/ 返回 user.post

重点:CBV的逻辑,找核心的dispatch方法,从url的as_views开始找。

rest_framework版本API

目录结构

代码详细

settings.py

INSTALLED_APPS = [
'
django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', # 安装app 'rest_framework', # 添加rest_framework,使错误信息变的好看,会用到这个 ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', # 不需要关闭csrf 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'm1day05.urls'
..........
..........
..........
STATIC_URL = '/static/' # REST_FRAMEWORK的所有相关配置都放在这里 REST_FRAMEWORK = {
  # 版本控制相关
"DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning", #还有其他的类,比如
QueryParameterVersioning
    'ALLOWED_VERSIONS':['v1','v2'],  # 支持的版本号

"DEFAULT_VERSION":'v1', # 默认的版本号
}

m1day05/urls.py

from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    # url(r'^admin/', admin.site.urls),
    url(r'^(?P<version>\w+)/user/', views.UserView.as_view()),   # 定义了版本控制的字段,必须填写版本!!!这块也是可以做路由分发的,下面的例子会是路由分发的模式
]

m1day05/views.py

from django.shortcuts import render,HttpResponse
from django.views import View
from rest_framework.views import APIView
from rest_framework.versioning import URLPathVersioning,QueryParameterVersioning
class UserView(APIView):  # 继承rest_framework的APIView,而不是原来CBV继承的VIEW

    def get(self,request,*args,**kwargs):
        # 做了封装,request是新request
        # request._request是老的request
        # print(request.authenticators)
        # print(self.versioning_class)
        self.dispatch   # 这块是用来看源码的入口
        print(request.version)
        return HttpResponse('user.get')

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

结果:

GET 访问 http://127.0.0.1:8000/v1/user/ user.get

POST访问 http://127.0.0.1:8000/v1/user/ 返回 user.post

十大特性

1,版本控制

第一步:添加参数

第二步:

第三步:

 详细例子参考上面 rest_framework版本API 的代码示例

备注

1,version 放在配置文件则全局使用,放在视图函数或者类中则单独使用

 

2,跨域问题解决

jsonp

cors 分为:

简单请求

复杂请求 先发送预检(options),然后在发送get/post

3,form表单只支持 get、post 两种方式,所以可以不遵循restful接口规范。

4,使用rest的Response,web好看,curl 加header json返回的就是字典

 

备注:

 Django Rest Framework源码流程:

  

2,用户认证

认证可以返回3种情况

1,返回(user,auth)的元组

2,触发异常(认证失败)

3,返回None(这种就是匿名用户)

目录结构

代码详细

m1day05/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',  # app
    'rest_framework',          # rest_framework
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',   #csrf可不关
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'm1day05.urls'
..........
..........
..........

STATIC_URL = '/static/'

REST_FRAMEWORK = {
    "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",
    'ALLOWED_VERSIONS':['v1','v2'],
    "DEFAULT_VERSION":'v1',
    # 认证相关,定义匿名用户
    "UNAUTHENTICATED_USER":None,  # 直接定义为None
    "UNAUTHENTICATED_TOKEN":None,
    # "UNAUTHENTICATED_USER":lambda :None,  # 定义为匿名函数,执行返回None
    # "UNAUTHENTICATED_TOKEN":lambda :None,
    # 认证相关,定义认证使用的类,如果在CBV的类中定义了authentication_classes,authentication_classes的优先级更高
    # 使用app01.utils.auth.TokenAuthtication这种字符串的方式定义,django会跟进字符串找到对应的类
    # 采用这种方式的话,就必须将TokenAuthtication类放在utils目录auth模块下
    'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.auth.TokenAuthtication',]
}

m1day05/urls.py

from django.conf.urls import url,include
urlpatterns = [
    # 将api标识和version版本控制放在这里,那么在路由分发的情况下,api标识和version版本控制在下面的app中都适用
    url(r'^api/(?P<version>\w+)/', include('app01.urls')),
]

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):
        """
        BaseAuthentication类中的authenticate这个方法如下:
        Authenticate the request and return a two-tuple of (user, token).
        raise NotImplementedError(".authenticate() must be overridden.")
        :param request:
        :return:
            (user,auth) 表示认证成功,并将元组分别复制给request.user/request.auth
             raise AuthenticationFailed('认证失败') 表示认证失败
             None, 表示匿名用户
        """
        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)  # return a two-tuple of (user, token)

app01/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):
    # 一般将token单独存放在一张表,当然存放在一起也是可以的,onetoone仅是拆表的效果
    user = models.OneToOneField('UserInfo')
    token = models.CharField(max_length=64)

表内容

 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()),
]

app01/views.py

# encoding:utf-8
from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from app01 import models
import uuid

class AuthView(APIView):
    authentication_classes = []  # 这个的优先级更高,这个会覆盖settings中定义的TokenAuthtication,所以这块不需要token验证
    def post(self,request,*args,**kwargs):
        response = {'code':1000} # 安装restful的规范,返回code + 内容
        user = request.data.get('username')  # request.data是从解析器中获取数据
        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})  # 使得返回信息显示中文

        token = str(uuid.uuid4())
        # update_or_create,没有则创建,存在则更新
        models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
        response['token'] = token

        return JsonResponse(response,json_dumps_params={'ensure_ascii':False})

class UserView(APIView):
    # 这里继承settings中定义的TokenAuthtication验证方式,所以需要token验证
    def get(self,request,*args,**kwargs):
        self.dispatch
        print(request.user)
        print(request.auth)
        return HttpResponse('user.get')

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

效果

执行如下脚本,生成token

import requests

ret = requests.post(
    url='http://127.0.0.1:8000/api/v1/auth/',
    json={
        'username':'alex',
        'password':'123'
    },
    headers={
        # 'Content-Type':'application/json'
    }
)
print(ret.text)

输出

{"code": 1000, "token": "f7c1622a-ae2f-4cf2-a121-0822e3d2fa87"}

GET 访问 http://127.0.0.1:8000/api/v1/user/?token=f7c1622a-ae2f-4cf2-a121-0822e3d2fa87 输出  user.get

POST 访问 http://127.0.0.1:8000/api/v1/user/?token=f7c1622a-ae2f-4cf2-a121-0822e3d2fa87 输出  user.post

携带错误token 访问 http://127.0.0.1:8000/api/v1/user/?token=f7c1622a-ae2f-4cf2-a121-0822e3dFSDFSF 显示 "detail": "token已失效或错误"

不带token访问 "detail": "用户Token未携带" 显示 detail": "用户Token未携带 

3,用户权限

权限的控制,返回True即有权限,返回False即没权限
必须经过认真后才可以判断权限,因为这个权限的判断是基于认真返回的元组中的request.a

目录结构

代码详细

m1day05/settings.py 同上用户认证

m1day05/urls.py 同上用户认证

app01/utils/auth.py 同上用户认证

app01/utils/permission.py

from rest_framework.permissions import BasePermission

class UserPermission(BasePermission):
    """
    权限的控制,返回True即有权限,返回False即没权限
    必须经过认真后才可以判断权限,因为这个权限的判断是基于认真返回的元组中的request.auth这个对象来判断权限的
    """
    def has_permission(self,request,view):
        print(request.user)  # 返回的是字符串的oldboy
        print(request.auth)  # 返回的是数据库中的对象(UserToken object)
        user_type_id = request.auth.user.user_type  # 可以根据返回的对象做表的关联,然后匹配user_type字段
        if user_type_id > 0:
            return True
        return False

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

app01/models.py

from django.db import models

class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)
    user_type_choices = (
        (1,'员工'),
        (2,'主管'),
    )
    user_type = models.IntegerField(choices=user_type_choices,default=1)  # 这样定义choice字段

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

表记录

 

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()),
    url(r'^salary/$', views.SalaryView.as_view()),   # 新增,这个url有权限控制
]

app01/views.py

from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from app01 import models
import uuid
from app01.utils.permission import UserPermission,ManagerPermission


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})

        token = str(uuid.uuid4())
        models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
        response['token'] = token

        return JsonResponse(response,json_dumps_params={'ensure_ascii':False})


class UserView(APIView):

    permission_classes = [UserPermission,]
    def get(self,request,*args,**kwargs):
        self.dispatch
        print(request.user)
        print(request.auth)
        return HttpResponse('user.get')

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


class SalaryView(APIView):
    # permission_classes 定义的权限判断类
    permission_classes = [UserPermission,ManagerPermission, ]
    def get(self,request,*args,**kwargs):

        return HttpResponse('...')

使用如下脚本分别为两个用户生成token

import requests
# oldboy: 6c5190f7-0d69-4abc-bf23-23bbfd5c6583
# alex : 27699bec-eba6-49e1-b276-d0bc2e65edc0
ret = requests.post(
    url='http://127.0.0.1:8000/api/v1/auth/',
    json={
        # 'username':'oldboy',
        'username':'alex',
        'password':'123'
    },
    headers={
        'Content-Type':'application/json'
    }
)
print(ret.text)

访问

alex的token访问 http://127.0.0.1:8000/api/v1/salary/?token=27699bec-eba6-49e1-b276-d0bc2e65edc0 显示  "detail": "You do not have permission to perform this action."

oldboy的token访问 http://127.0.0.1:8000/api/v1/salary/?token=6c5190f7-0d69-4abc-bf23-23bbfd5c6583 显示 .... 

4. 用户访问频率控制、节流

实现原理:记录每次访问的时间戳,并记录到列表中,然后计算当前请求的时间戳时间和列表中倒数第一个的时间差,于限流的时间单位做比较

                {
                    throttle_fengfeng: [1527322520.5065649, 1527322512.5065649,1527322509.5065649,],
                }
                
                
                
                第一步:去列表中pop已经失效
                        
                        1527322599.5065649 - 60 = 1527322539.5065649
                        
                        fengfeng:[]
                        
                第二步:计算个数:
                        fengfeng:[1527322599.5065649,]
                

源码逻辑参考:

目录结构

代码详细

m1day05/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
    'rest_framework',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

.......
.......
.......

# 添加缓存相关配置
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': 'D:\m1\m1day05\cache',
    }
}


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

STATIC_URL = '/static/'

REST_FRAMEWORK = {
    "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",
    'ALLOWED_VERSIONS':['v1','v2'],
    "DEFAULT_VERSION":'v1',

    "UNAUTHENTICATED_USER":None,
    "UNAUTHENTICATED_TOKEN":None,
    # "UNAUTHENTICATED_USER":lambda :None,
    # "UNAUTHENTICATED_TOKEN":lambda :None,

    'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.auth.TokenAuthtication',],

    # 添加限流相关的全局配置,可以在逻辑中根据用户角色在调整
    'DEFAULT_THROTTLE_RATES':{
        'user':'3/m'  # 这块写min也行,源码中取的是第一个字母,同样的支持s、m、h、d,但不支持每3h这样的限速
    }
}

m1day05/urls.py 同上用户权限

app01/utils/auth.py 同上用户权限

app01/utils/permission.py 同上用户权限

app01/utils/throttle.py

from rest_framework.throttling import SimpleRateThrottle

class UserRateThrottle(SimpleRateThrottle):
    scope = 'user' # 这个是全局配置文件中定义的'user':'3/m'
    def get_cache_key(self, request, view):
        if request.user:
            # 如果已经登录,用户名作为缓存的key
            ident = request.user
        else:
            # 如果没有登录,IP作为缓存的key
            ident = self.get_ident(request)
        # 'throttle_%(scope)s_%(ident)s'  # 给缓存的key加个标识,以方便看出
        #  throttle_user_fengfeng
        return self.cache_format % {
            'scope': self.scope,
            'ident': ident
        }

    def allow_request(self, request, view):
        if request.auth.user.user_type == 1:
            # 在这块的逻辑,可以根据用户的权限,配置不同的限流开关,默认是走settings.py全局中的限制
            # self.num_requests = 3
            # self.duration = 60
            pass
        else:
            self.num_requests = 6   # 对应全局配置中 3/m 中的数字3
        return super(UserRateThrottle,self).allow_request(request, view)

app01/models.py 同上

表记录

 

app01/urls.py同上

app01/views.py

from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from app01 import models
import uuid
from app01.utils.permission import UserPermission,ManagerPermission
from app01.utils.throttle import UserRateThrottle
from rest_framework.versioning import BaseVersioning
from rest_framework.authentication import BaseAuthentication
from rest_framework.permissions import BasePermission
from rest_framework.throttling import BaseThrottle


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})

        token = str(uuid.uuid4())
        models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
        response['token'] = token

        return JsonResponse(response,json_dumps_params={'ensure_ascii':False})


class UserView(APIView):

    permission_classes = [UserPermission,]
    throttle_classes = [UserRateThrottle, ]

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

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

class SalaryView(APIView):
    # 这里是应用权限
    permission_classes = [UserPermission,ManagerPermission, ]
    # 这里是应用限流
    throttle_classes = [UserRateThrottle,]
    def get(self,request,*args,**kwargs):
        return HttpResponse('...')

效果:

根据如下脚本获取token

import requests
# alex : 8fe765fc-661f-4d6d-924f-cfc5c4013293
# oldboy: b596cab8-3991-448b-9308-80efb253ddc9
ret = requests.post(
    url='http://127.0.0.1:8000/api/v1/auth/',
    json={
        # 'username':'oldboy',
        'username':'alex',
        'password':'123'
    },
    headers={
        'Content-Type':'application/json'
    }
)
print(ret.text)

访问

1,alex的token 访问 http://127.0.0.1:8000/api/v1/salary/?token=8fe765fc-661f-4d6d-924f-cfc5c4013293 提示:"detail": "You do not have permission to perform this action."

因为alex的user_type 不大于1,所以没有权限

2,oldboy的token访问6次 http://127.0.0.1:8000/api/v1/salary/?token=b596cab8-3991-448b-9308-80efb253ddc9 后出现   "detail": "Request was throttled. Expected available in 58 seconds."

注意,这个时间每次访问是会变化的

5. 序列化

支持两种序列化

# 继承自serializers.Serializer的序列化

# 继承自serializers.ModelSerializer的序列化

目录结构

代码详细

m1day05/settings.py 同上,但是不依赖上面settings中的配置

m1day05/urls.py 同上用户权限

app01/utils/auth.py 同上用户权限【不依赖】

app01/utils/permission.py 同上用户权限【不依赖】

app01/utils/throttle.py 同上用户权限【不依赖】

app01/models.py

from django.db import models


class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)
    user_type_choices = (
        (1,'员工'),
        (2,'主管'),
    )
    user_type = models.IntegerField(choices=user_type_choices,default=1)


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


class Host(models.Model):  # 序列化和分页用的是这个model
    hostname = models.CharField(max_length=32)
    port = models.IntegerField(default=80)

    host_types = (
        (1,'web'),
        (2,'存储'),
    )
    htype = models.IntegerField(choices=host_types,default=1)

    sa = models.ForeignKey('Sa')
    departs = models.ManyToManyField('Depart')


class Depart(models.Model):  # 解析器用的是这个model
    title = models.CharField(max_length=32)


class Sa(models.Model):  # 路由用的是这个model
    name = models.CharField(max_length=32)

表数据如下

 

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()),
    # url(r'^salary/$', views.SalaryView.as_view()),
    url(r'^host/$', views.HostView.as_view()),   # 这行生效
    # url(r'^depart/$', views.DepartView.as_view()),
    # url(r'^parser/$', views.ParserView.as_view()),
    # url(r'^pager/$', views.PagerView.as_view()),

    # 列表: SaView.get
    # 添加:  SaView.post
    # url(r'^sa/$', views.SaView.as_view()),
    # 详细 : SaView.get
    # 修改 : SaView.put
    # 局部 : SaView.patch
    # 删除 : SaView.delete
    # url(r'^sa/(?P<pk>\d+)/$', views.SaView.as_view()),

]

app01/views.py

对QuerySet、对象进行序列化

v1: # 继承自serializers.Serializer的序列化

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

# 继承自serializers.Serializer的序列化
class HostSerializer(serializers.Serializer):
    # 定义序列化
    hostname = serializers.CharField()
    port = serializers.IntegerField()
    sa_id = serializers.CharField(source='sa.id')  # source指定关联的源字段信息
    sa_name = serializers.CharField(source='sa.name')
    htype_id = serializers.CharField(source='htype')
    htype_title = serializers.CharField(source='get_htype_display')  # 获取choice中数字对应的中文
    departs = serializers.SerializerMethodField()  # 指定回调函数

    def get_departs(self,obj):  # 定义上面字段对应的回调函数
        return [{'id':row.id,'title':row.title} for row in obj.departs.all()]


class HostView(APIView):
    authentication_classes = []

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

        ret = {'code':2001}
        try:
            # 序列化多个对象
            queryset = models.Host.objects.all()
            ser = HostSerializer(instance=queryset,many=True)  # 使用自定义的序列化来序列化queryset集合
            # 序列化单个对象
            # obj = models.Host.objects.all().first()
            # ser = HostSerializer(instance=obj, many=False)  # 使用自定义的序列化来序列化单个queryset对象
            ret['data'] = ser.data
        except Exception as e:
            ret['code'] = 2002
            ret['msg'] = '获取数据失败'
        return Response(ret)

访问http://127.0.0.1:8000/api/v1/host/,返回

{
    "code": 2001,
    "data": [
        {
            "hostname": "c1.com",
            "port": 80,
            "sa_id": "1",
            "sa_name": "峰峰",
            "htype_id": "1",
            "htype_title": "web",
            "departs": [
                {
                    "id": 1,
                    "title": "公关部"
                },
                {
                    "id": 2,
                    "title": "市场部"
                }
            ]
        },
        {
            "hostname": "c2.com",
            "port": 80,
            "sa_id": "1",
            "sa_name": "峰峰",
            "htype_id": "1",
            "htype_title": "web",
            "departs": [
                {
                    "id": 1,
                    "title": "公关部"
                },
                {
                    "id": 2,
                    "title": "市场部"
                },
                {
                    "id": 3,
                    "title": "销售部"
                }
            ]
        },
        {
            "hostname": "c3.com",
            "port": 80,
            "sa_id": "2",
            "sa_name": "银秋良",
            "htype_id": "1",
            "htype_title": "web",
            "departs": [
                {
                    "id": 1,
                    "title": "公关部"
                },
                {
                    "id": 3,
                    "title": "销售部"
                }
            ]
        },
        {
            "hostname": "c4.com",
            "port": 80,
            "sa_id": "2",
            "sa_name": "银秋良",
            "htype_id": "1",
            "htype_title": "web",
            "departs": [
                {
                    "id": 2,
                    "title": "市场部"
                },
                {
                    "id": 3,
                    "title": "销售部"
                }
            ]
        }
    ]
}
View Code

v2: # 继承自serializers.ModelSerializer的序列化【更简单】

from rest_framework.views import APIView
from rest_framework.response import Response
from app01 import models
from rest_framework import serializers
# 继承自serializers.ModelSerializer的序列化 class NewHostSerializer(serializers.ModelSerializer): htype_title = serializers.CharField(source='get_htype_display') xxxxxxx = serializers.SerializerMethodField() # 指定回调函数 class Meta: model = models.Host # fields = '__all__' ,这种方式拿不到choice中数字对应的中文 # 可以配合exclude结合使用
    
# exclude=["authors",] #显示authors以外的字段(单独使用)
        fields = ['hostname','port','htype','htype_title','sa','departs','xxxxxxx']
        # depth代表关联表的层级(根据foreignkey 一级一级的找),默认是0,范围 0-10,特别耗性能一般不建议使用
        # depth获得的数据,也可以通过可以自定义回调函数SerializerMethodFiel的方式获取
        depth = 1

    def get_xxxxxxx(self, obj):  # 定义上面字段对应的回调函数
        """
        get_xxxxxxx的效果和depth = 1 的效果一样
        :param obj:
        :return:
        """
        return [{'id': row.id, 'title': row.title} for row in obj.departs.all()]

class HostView(APIView):
    authentication_classes = []

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

        ret = {'code':2001}
        try:
            queryset = models.Host.objects.all()
            ser = NewHostSerializer(instance=queryset,many=True) # 使用自定义的序列化来序列化queryset集合
            #     obj = models.Host.objects.all().first()
            #     ser = NewHostSerializer(instance=obj, many=False) # 使用自定义的序列化来序列化单个queryset对象
            ret['data'] = ser.data
        except Exception as e:
            ret['code'] = 2002
            ret['msg'] = '获取数据失败'
        return Response(ret)

 访问 http://127.0.0.1:8000/api/v1/host/

返回

{
    "code": 2001,
    "data": [
        {
            "hostname": "c1.com",
            "port": 80,
            "htype": 1,
            "htype_title": "web",
            "sa": {
                "id": 1,
                "name": "峰峰"
            },
            "departs": [
                {
                    "id": 1,
                    "title": "公关部"
                },
                {
                    "id": 2,
                    "title": "市场部"
                }
            ],
            "xxxxxxx": [
                {
                    "id": 1,
                    "title": "公关部"
                },
                {
                    "id": 2,
                    "title": "市场部"
                }
            ]
        },
        {
            "hostname": "c2.com",
            "port": 80,
            "htype": 1,
            "htype_title": "web",
            "sa": {
                "id": 1,
                "name": "峰峰"
            },
            "departs": [
                {
                    "id": 1,
                    "title": "公关部"
                },
                {
                    "id": 2,
                    "title": "市场部"
                },
                {
                    "id": 3,
                    "title": "销售部"
                }
            ],
            "xxxxxxx": [
                {
                    "id": 1,
                    "title": "公关部"
                },
                {
                    "id": 2,
                    "title": "市场部"
                },
                {
                    "id": 3,
                    "title": "销售部"
                }
            ]
        },
        {
            "hostname": "c3.com",
            "port": 80,
            "htype": 1,
            "htype_title": "web",
            "sa": {
                "id": 2,
                "name": "银秋良"
            },
            "departs": [
                {
                    "id": 1,
                    "title": "公关部"
                },
                {
                    "id": 3,
                    "title": "销售部"
                }
            ],
            "xxxxxxx": [
                {
                    "id": 1,
                    "title": "公关部"
                },
                {
                    "id": 3,
                    "title": "销售部"
                }
            ]
        },
        {
            "hostname": "c4.com",
            "port": 80,
            "htype": 1,
            "htype_title": "web",
            "sa": {
                "id": 2,
                "name": "银秋良"
            },
            "departs": [
                {
                    "id": 2,
                    "title": "市场部"
                },
                {
                    "id": 3,
                    "title": "销售部"
                }
            ],
            "xxxxxxx": [
                {
                    "id": 2,
                    "title": "市场部"
                },
                {
                    "id": 3,
                    "title": "销售部"
                }
            ]
        }
    ]
}
View Code

对用户请求数据进行校验

app01/views.py

from rest_framework import serializers
class DepartSerializer(serializers.Serializer):
    title = serializers.CharField(min_length=6,max_length=18)

    # 验证函数的格式:validate_字段名称
    def validate_title(self,value):
        from rest_framework import exceptions
        if value == 'alexxxx':
            raise exceptions.ValidationError('姓名不能是傻逼')
        return value

class DepartView(APIView):
    authentication_classes = []

    def post(self,request,*args,**kwargs):
        ret = {'code':3001}
        try:
            ser = DepartSerializer(data=request.data)
            if ser.is_valid(): # 通过对象.is_valid()方法进行验证
                # 验证成功
                models.Depart.objects.create(**ser.data)  # 创建的时候传递 **ser.data
            else:
                ret['code'] = 3002
                ret['msg'] = ser.errors
        except Exception as e:
            ret['code'] = 3003
            ret['msg'] = '添加失败'
        return Response(ret)

验证函数:validate_字段名称,然后调用 对象.is_valid() 方法来验证。

 

 

validate_title 钩子函数,类似clean_

 

6. 解析器

- 根据请求头Content-Type的值,进行请求体数据的解析。

支持多种解析器

 

from rest_framework.parsers import JSONParser,FormParser

 

但是默认一般默认只支持json即可

 

只需要在settings.py中添加如下

REST_FRAMEWORK = {
    .......'DEFAULT_PARSER_CLASSES':['rest_framework.parsers.JSONParser',],
  .......
}

app01/urls.py

urlpatterns = [
    .......
    url(r'^parser/$', views.ParserView.as_view()),
    .......
]

app01/views.py

class ParserView(APIView):
    authentication_classes = []

    def post(self,request,*args,**kwargs):
        self.dispatch
        print(request.data,type(request.data))
        return Response('okok 666')

postman模拟请求头访问

指定 'Content-Type':'application/json' 才可以

7. 分页

支持多种分页方式:

from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination,CursorPagination

代码详细

PageNumberPagination方式

在settings.py中指定

REST_FRAMEWORK = {
    .........'PAGE_SIZE':2
}

app01/urls.py

from django.conf.urls import url
from app01 import views
urlpatterns = [
  ......
    url(r'^pager/$', views.PagerView.as_view()),
]

app01/views.py

from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination,CursorPagination
class MyPageNumberPagination(PageNumberPagination):
    page_size_query_param = 'size' # 这个定义了如果想显示指定每页显示的条目数的参数,默认使用settings.py全局中定义的PAGE_SIZE
    max_page_size = 10  # 这个定义了每个分页最大的条目数,防止一页10w条数据这样的请求
    #  Client can control the page using this query parameter.
    page_query_param = 'page'  # 这个是从 PageNumberPagination 这个类里面获取,并在MyPageNumberPagination这个子类中定义的
    # 也可以不设置page_query_param,则默认用父类中指定的


class PagerSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Host
        fields = ['hostname','port',]

class PagerView(APIView):
    authentication_classes = []
    def get(self,request,*args,**kwargs):
        queryset = models.Host.objects.all().order_by('id')
        pg = MyPageNumberPagination()
        ret = pg.paginate_queryset(queryset=queryset,request=request,view=self)
        ser = PagerSerializer(instance=ret,many=True)
        return Response(ser.data)

效果,请求的时候可以同时指定size【每页显示的个数,如果不指定默认从settings.py中读取PAGE_SIZE】参数和page参数【第几页】

不指定size参数

指定size参数

CursorPagination方式

app01/views.py

from rest_framework.pagination import CursorPagination


class MyCursorPagination(CursorPagination):
    page_size = 1
    ordering = 'id'  # 默认是根据_create 排序的,所以这个改成自己的字段


class PagerSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Host
        fields = ['hostname','port',]


class PagerView(APIView):
    authentication_classes = []

    def get(self,request,*args,**kwargs):
        queryset = models.Host.objects.all().order_by('id')
        pg = MyCursorPagination()
        ret = pg.paginate_queryset(queryset=queryset,request=request,view=self)
        ser = PagerSerializer(instance=ret,many=True)

        response = {
            'code':1000,
            'next':pg.get_next_link(),  # 下一页链接
            'prev':pg.get_previous_link(), # 上一页链接
            'data':ser.data
        }

        return Response(response)

postman访问示例

应用场景

cursor 页数加密的应用场景
数据库1000条
数据库1000w条 越往后扫描越慢,页数控制最多200页,博客园和抽屉

如果扫10000-10010,快速的方法是 添加where id > 9999 然后在进行limit offset,但是直接page=1000000000也不可以

最终的解决是:  最小id <  查找 < 最大id
cursor 会记录当前页,和最小id和最大id

8. 路由(自动化路由)

一般不建议使用

# from rest_framework import routers
# router = routers.DefaultRouter()
# router.register(r'sa', views.SaView)

9. 视图

from django.views import View
from rest_framework.views import APIView           #所有功能都没有,自己定义【适用于复杂的逻辑】
from rest_framework.generics import GenericAPIView # 如果视图继承我以及我的子孙,路由中必须添加桉树
from rest_framework.viewsets import GenericViewSet
from rest_framework.viewsets import ModelViewSet   #所有功能都有,无需自己定义【使用于仅简单的增删改查】

继承自APIView

代码详细

app01/urls.py

from django.conf.urls import url
from app01 import views
urlpatterns = [
    # .......
    # 列表: SaView.get
    # 添加:  SaView.post
    url(r'^sa/$', views.SaView.as_view()),
    # 详细 : SaView.get
    # 修改 : SaView.put
    # 局部 : SaView.patch
    # 删除 : SaView.delete
    url(r'^sa/(?P<pk>\d+)/$', views.SaView.as_view()),

]

app01/views.py

class SaSerialiser(serializers.ModelSerializer):
    class Meta:
        model = models.Sa
        fields = "__all__"


class SaView(APIView):
    authentication_classes = []

    def get(self,request,*args,**kwargs):
        pk = kwargs.get('pk')
        if not pk:
            queryset = models.Sa.objects.all()
            ser = SaSerialiser(instance=queryset,many=True)
        else:
            obj = models.Sa.objects.filter(id=pk).first()
            ser = SaSerialiser(instance=obj, many=False)
        return Response(ser.data)

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

    def put(self,request,*args,**kwargs):
        kwargs.get('pk')
        pass

    def patch(self,request,*args,**kwargs):
        kwargs.get('pk')
        pass

    def delete(self,request,*args,**kwargs):
        kwargs.get('pk')
        pass

访问单个详情

访问所有元素

继承自ModelViewSet

app01/urls.py

urlpatterns = [
    ######APIView
    # # 列表: SaView.get
    # # 添加:  SaView.post
    # url(r'^sa/$', views.SaView.as_view()),
    # # 详细 : SaView.get
    # # 修改 : SaView.put
    # # 局部 : SaView.patch
    # # 删除 : SaView.delete
    # url(r'^sa/(?P<pk>\d+)/$', views.SaView.as_view()),

######看这里ModelViewSet # 列表: SaView.get # 添加: SaView.post url(r'^sa/$', views.SaView.as_view({'get':'list','post':'create'})), url(r'^sa\.(?P<format>[a-z0-9]+)$', views.SaView.as_view({'get':'list','post':'create'})), # 支持后缀.json的形式访问 # 详细 : SaView.get # 修改 : SaView.put # 局部 : SaView.patch # 删除 : SaView.delete url(r'^sa/(?P<pk>\d+)/$', views.SaView.as_view({'get':"retrieve",'put':'update','patch':'partial_update','delete':'destroy'})), url(r'^sa/(?P<pk>\d+)\.(?P<format>[a-z0-9]+)$', views.SaView.as_view({'get':"retrieve",'put':'update','patch':'partial_update','delete':'destroy'})), # url(r'^', include(router.urls)), ]

put对所有字段修改
patch 对单个字段修改

app01/views.py

class SaSerialiser(serializers.ModelSerializer):
    class Meta:
        model = models.Sa
        fields = "__all__"

# from django.views import View
# from rest_framework.views import APIView
# from rest_framework.generics import GenericAPIView
# from rest_framework.viewsets import GenericViewSet
from rest_framework.viewsets import ModelViewSet
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer,AdminRenderer
class SaView(ModelViewSet):
    authentication_classes = []
    renderer_classes = [JSONRenderer,BrowsableAPIRenderer]  # 这个指定的是渲染器,一般可不用,BrowsableAPIRendere不能单独存在
    queryset = models.Sa.objects.all()
    serializer_class = SaSerialiser

如果只使用增加,则只继承一个类即可

class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):

访问http://127.0.0.1:8000/api/v1/sa/

访问http://127.0.0.1:8000/api/v1/sa.json

10. 渲染器

如下就是渲染器

全局使用渲染器

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

局部使用渲染器

from rest_framework.viewsets import ModelViewSet
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer,AdminRenderer,DocumentationRenderer  # 这个是导入渲染器
class SaView(ModelViewSet):
    authentication_classes = []
    renderer_classes = [JSONRenderer,BrowsableAPIRenderer]  # 局部范围使用渲染器,BrowsableAPIRendere不能单独存在
    queryset = models.Sa.objects.all()
    serializer_class = SaSerialis

自动生成API文档

http://www.django-rest-framework.org/topics/documenting-your-api/

 

 知识点补充

1,使用json返回汉字而不是unide字符串,添加参数 ensure_ascii=False

示例

# encoding:utf-8
ret = {
    'name':'理解'
}
import json
v1 = json.dumps(ret)
v2 = json.dumps(ret,ensure_ascii=False)
print(v1)
print(v2)

返回结果

{"name": "\u7406\u89e3"}
{"name": "理解"}

在django中使用如下 json_dumps_params={'ensure_ascii':False} 来实现相同的效果

JsonResponse(response,json_dumps_params={'ensure_ascii':False})  # response是要返回的字典

 强制返回json格式,渲染器时:请求?format = json

 

参考:https://www.cnblogs.com/wupeiqi/articles/7805382.html extra_args 给上面的字段传参数

 

 

RESTful 规范

https://blog.csdn.net/aaronthon/article/details/81714497

https://blog.csdn.net/aaronthon/article/details/81714495

 

posted @ 2018-05-26 12:24  番茄土豆西红柿  阅读(271)  评论(0编辑  收藏  举报
TOP