阿里山QQ

导航

django restfulwork 源码剖析

概要:
  1.restful 规范(建议);
  2. django rest framework框架
内容回顾:
  1.开发模式;
    - 普通开发模式(前后端放在一起写)
    - 前后端分离
      好处: 后端一套,前端使用app,pc等;
  2. 后端开发
    为前端提供URL(API的开发)
    注:永远返回Httpresponse
  3. django Fbv 和Cbv
 
一、Django CBV和FBV
在视图中,cbv支持四中method,如下:
from django.views import View
class StudentView(View):

    def get(self,request):
        return HttpResponse('GET')

    def post(self,request):
        return HttpResponse('POST')

    def put(self,request):
        return HttpResponse('POST')

    def delete(self,request):
        return HttpResponse('DELETE')

  

那么url必须这么写:

from app01 import views
urlpatterns = [
    url(r'^students/$', views.StudentView.as_view()),
]

 

使用postman进行测试:

 

查看源码预备知识:封装

#!/usr/bin/env python
# -*- coding: utf-8 -*-

class Request(object):

    def __init__(self, obj):
        self.obj = obj

    @property
    def user(self):
        return self.obj.authticate()

class Auth(object):

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def authticate(self):
        return self.name


class APIView(object):

    def dispatch(self):
        self.f2()

    def f2(self):
        a = Auth('charles', 18)
        req = Request(a)
        print(req.user)


obj = APIView()
obj.dispatch()

  

 CBV实现原理: 在View类中有一个dispatch方法,在每个请求到达之后,会先执行,获取请求的method,然后通过反射,执行子类中对应的方法;

from django.views import View
class StudentView(View):

    def dispatch(self, request, *args, **kwargs):
        # return HttpResponse('dispatch')
        func = getattr(self,request.method.lower())
        ret = func(request, *args, **kwargs)
        return ret

    def get(self,request):
        return HttpResponse('GET')

    def post(self,request):
        return HttpResponse('POST')

    def put(self,request):
        return HttpResponse('POST')

    def delete(self,request):
        return HttpResponse('DELETE')

  

 由上面的例子可以看到,dispatch方法是所有使用CBV的视图必须使用到的方法,为了避免每一个类都实现这个方法,可以通过类的继承,来避免代码的重复:

在下面的例子中,基类MyBaseView实现了一个dispatch方法,子类StudentView实现了继承了MyBaseView和View两个类,在实例化StudentView,并执行其方法的时候,会先在StudentView中寻找dispatch方法,如果没有,会去MyBaseView中去寻找,MyBaseView没有父类,所以会看是self是谁,然后从self的另外一个父类View中去寻找; 顺序是StudentView-->MyBaseView-->View

from django.views import View

class MyBaseView(object):
    def dispatch(self,request, *args, **kwargs):
        print('before')
        ret = super(MyBaseView, self).dispatch(request, *args, **kwargs)
        print('after')
        return ret


class StudentView(MyBaseView,View):

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

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

    def put(self,request,*args, **kwargs):
        return HttpResponse('POST')

    def delete(self,request,*args, **kwargs):
        return HttpResponse('DELETE')

  

 二、Django中间件

1. 中间件执行顺序(中间件最多可以实现5个方法)

  正常顺序:  执行所有process_request-->路由匹配-->执行所有process_view-->执行视图函数-->process_response

       如果有报错: 执行所有process_request-->路由匹配-->执行所有process_view-->执行视图函数-->process_response

       如果视图函数有render:  执行所有process_request-->路由匹配-->执行所有process_view-->执行视图函数-->process_response/process_render_template;

 

2. 使用中间件做过什么?

       - 权限

  - 用户登录验证

  - django csrf token

   那么用户的csrf token是如何实现的? 

     FBV:在django中,csrf token检测是在process_view方法中实现的,会检查视图是否使用@csrf_exempt,然后去请求体或者cookie中获取token;

       如果不使用中间件做csrf token认证,那么可以加@csrf_protect,对指定的实视图做验证;

     CBV: 有两种实现方法csrf_exempt

from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator

class StudentView(View):

    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super(StudentView,self).dispatch(request, *args, **kwargs)

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

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

    def put(self,request,*args, **kwargs):
        return HttpResponse('POST')

    def delete(self,request,*args, **kwargs):
        return HttpResponse('DELETE')

 

from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator

@method_decorator(csrf_exempt, name='dispatch')
class StudentView(View):
    def get(self,request,*args, **kwargs):
        return HttpResponse('GET')

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

    def put(self,request,*args, **kwargs):
        return HttpResponse('POST')

  

三、restful 规范

1.根据method 不同,做不同的操作

url(r'^order/$', views.order),

def order(request):
    if request.method == 'GET':
        return HttpResponse('获取订单')
    elif request.method == 'POST':
        return HttpResponse('创建订单')
    elif request.method == 'PUT':
        return HttpResponse('更新订单')
    elif request.method == 'DELETE':
        return HttpResponse('删除订单')

 

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

 

四、restframework

使用自定义的类,实现API 认证; 具体看源码,和CBV 执行流程类似;

from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework import exceptions
class MyAuthenticate(object):

    def authenticate(self,request):
        token = request._request.GET.get('token')
        if not token:
            raise exceptions.AuthenticationFailed('用户认证失败')

    def authenticate_header(self, val):
        pass


class DogView(APIView):
    authentication_classes = [MyAuthenticate, ]

    def get(self,request,*args, **kwargs):
        ret = {
            'code': 1000,
            'msg': 'xxx'
        }
        return HttpResponse(json.dumps(ret),status=201)

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

    def put(self,request,*args, **kwargs):
        return HttpResponse('POST')

    def delete(self,request,*args, **kwargs):
        return HttpResponse('DELETE')  

 需要掌握的内容:

1.中间件

2.CBV

3.csrf

4.规范

5.djangorestframework

  - 如何验证(基于数据库实现用户认证)

  -源码流程(面向对象回顾流程)

 

五、restframework之登录

问题: 有些API用户登录之后才可以访问,有些不需要用户登录;

先创建两张表

class UserInfo(models.Model):   # 用户表,存储用户信息
    user_type_choices = (
        (1,'普通用户'),
        (2,'VIP'),
        (3,'SVIP'),
    )
    user_type = models.IntegerField(choices=user_type_choices)
    username = models.CharField(max_length=32, unique=True)
    password = models.CharField(max_length=64)

class UserToken(models.Model):    # 存储用户登录成功之后的token
    user = models.OneToOneField(to='UserInfo')
    token = models.CharField(max_length=64)

 

编写API

url(r'^api/v1/auth/$', views.AuthView.as_view())


from django.http import JsonResponse

# Create your views here.

from rest_framework.views import APIView
from api import models

def md5(user):
    import hashlib
    import time

    ctime = str(time.time())
    m = hashlib.md5(bytes(user, encoding='utf-8'))
    m.update(bytes(ctime, encoding='utf-8'))
    return m.hexdigest()


class AuthView(APIView):
"""
用于用户登录认证
""" def post(self, request, *args, **kwargs): ret = {'code': 10000, 'msg': None } try: user = request._request.POST.get('username') pwd = request._request.POST.get('password') obj = models.UserInfo.objects.filter(username=user,password=pwd).first() if not obj: ret['code'] = 1001 ret['msg'] = '用户名或密码错误' # 为登录用户创建token token = md5(user) # 用户token存在就更新,不存在就创建 models.UserToken.objects.update_or_create(user=obj, defaults={'token': token}) ret['token'] = token # 将token返回给用户 except Exception as e: ret['code'] = 1002 ret['msg'] = '请求异常' return JsonResponse(ret)

 

使用postman发送请求进行验证

 

六、 rest framework之基于token实现基本用户认证

上面的例子是用户通过访问登录认证的API,获取返回的token,并将token存储到token表中;

用户拿到这个token之后,就可以访问其他的API了;

from rest_framework.views import APIView
from rest_framework.views import exceptions
from rest_framework.authentication import BaseAuthentication
from api import models

ORDER_DICT = {
    1: {
        'name': 'charles',
        'age': 18,
        'gender': '男',
        'content': '....'
    },
    2: {
        'name': '男',
        'age': 19,
        'gender': '男',
        'content': '......'
    },
}


class Authtication(object):

    def authenticate(self, request):
        token = request._request.GET.get('token')
        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用户认证失败')
        # 在restframework内部会将整个两个字段赋值给request,以供后续继续使用
        return (token_obj.user, token_obj)

    def authenticate_header(self, request):
        pass


class OrderView(APIView):
    """
    订单相关业务
    """
    authentication_classes = [Authtication, ]   # 在访问API的时候,先走这个用户认证的类

    def get(self, request, *args, **kwargs):
        ret = {'code': 1000, 'msg': None, 'data': None}
        # request.user --> token_obj.user
        # request.auth --> token_obj
        try:
            ret['data'] = ORDER_DICT
        except Exception as e:
            pass
        return JsonResponse(ret)

 

七、rest framework之认证基本流程源码分析

1、请求进来之后,会先执行dispatch()方法;

class OrderView(APIView):
    """
    订单相关业务
    """
    authentication_classes = [Authtication, ]

    def get(self, request, *args, **kwargs):
        self.dispatch()      # 使用pycharm进入dispatch方法,查看源码

2、先对request进行封装

self.args = args
        self.kwargs = kwargs
        request = self.initialize_request(request, *args, **kwargs)   # 对request进行封装,点击继续查看该方法

    def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        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
        )

    def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        return [auth() for auth in self.authentication_classes]   # 通过列表生成式,执行self.authentication_classes方法,因为子类中没有这个方法,
那么会执行父类中的这个方法; 点击继续查看; authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES # 这个是存在于父类中的; 我们可以在自己的实例化的子类中使用自定义的类,替代这个类; 默认升是
BaseAuthentication
                                                                                                                                    

3、认证

self.initial(request, *args, **kwargs)    # 继续点击查看;

self.perform_authentication(request)  # 实现认证;

    def perform_authentication(self, request):
        request.user   # 执行request.user



    @property
    def user(self):
        """
        Returns the user associated with the current request, as authenticated
        by the authentication classes provided to the request.
        """
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                # 获取认证对象,进行进一步认证
                self._authenticate()
        return self._user


    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        # 循环所有authenticator对象
        for authenticator in self.authenticators:
            try:
                 # 执行authenticate方法
                 # 1.如果authenticate 方法抛出异常,self._not_authenticated执行
                 # 2. 没有抛出异常,有返回值,必须是元祖:(request.user, request.auth)
                 # 3. 返回None,我不管,下一个认证进行处理;
                 # 4.如果都返回None,执行self._not_authenticated(),返回(AnonymousUser, None)
                user_auth_tuple = authenticator.authenticate(self)
            except exceptions.APIException:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:
                self._authenticator = authenticator
                # request.user和request.auth
                self.user, self.auth = user_auth_tuple
                return



    def _not_authenticated(self):
        """
        Set authenticator, user & authtoken representing an unauthenticated request.

        Defaults are None, AnonymousUser & None.
        """
        self._authenticator = None

        if api_settings.UNAUTHENTICATED_USER:     # AnonymousUser 匿名用户
            self.user = api_settings.UNAUTHENTICATED_USER()
        else:
            self.user = None

        if api_settings.UNAUTHENTICATED_TOKEN:
            self.auth = api_settings.UNAUTHENTICATED_TOKEN()    # None
        else:
            self.auth = None

 简要流程图如下:

 

 八、rest framework之匿名用户配置

1.认证类的全局配置(全局使用)

authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES  # 认证类的默认配置

api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)


def reload_api_settings(*args, **kwargs):
    setting = kwargs['setting']
    if setting == 'REST_FRAMEWORK':    # 获取settings的REST_FRAMEWORK 的配置项
        api_settings.reload()

 

在settings中定义 REST_FRAMEWORK 配置

REST_FRAMEWORK  =  {
    'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.auth.FirstAuthtication', 'api.utils.auth.Authtication']
}


# 将认证的类,放入到上面配置的路径里面

# 视图函数中不包含上述的认证的类,并且要将登陆的API的authentication_classes设为空;
class AuthView(APIView):
    authentication_classes = []
    def post(self, request, *args, **kwargs):
            pass


class OrderView(APIView):
    """
    订单相关业务
    """

    def get(self, request, *args, **kwargs):
        ret = {'code': 1000, 'msg': None, 'data': None}
        # request.user --> token_obj.user
        # request.auth --> token_obj
        try:
            ret['data'] = ORDER_DICT
        except Exception as e:
            pass
        return JsonResponse(ret)

 

2、在用户没有登录(匿名用户的时候)

    def _not_authenticated(self):
        """
        Set authenticator, user & authtoken representing an unauthenticated request.

        Defaults are None, AnonymousUser & None.
        """
        self._authenticator = None

        if api_settings.UNAUTHENTICATED_USER:    # 获取settings中的用户默认用户是啥
            self.user = api_settings.UNAUTHENTICATED_USER()
        else:
            self.user = None

        if api_settings.UNAUTHENTICATED_TOKEN:    # 获取settings中的默认token是啥
            self.auth = api_settings.UNAUTHENTICATED_TOKEN()
        else:
            self.auth = None

 

没有通过认证,默认为匿名用户

class UserInfo(APIView):

    authentication_classes = []
    def get(self,request,  *args, **kwargs):
        ret = {'code': 1000, 'msg': None, 'data': None}
        print(request.user)
        return JsonResponse(ret

# 打印结果
AnonymousUser


# 在settings.py中增加如下配置,未登录时,用户和auth都为None,方面后续判断用户是否登录;
REST_FRAMEWORK  =  {
    'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.auth.FirstAuthtication', 'api.utils.auth.Authtication'],
    'UNAUTHENTICATED_USER':None,  #request.user
    'UNAUTHENTICATED_TOKEN':None  # request.auth   
}

  

九、rest framework之内置基本认证

1.BaseAuthentication 基类,可以规范自定义的认证的类

from rest_framework.authentication import BaseAuthentication, BasicAuthentication


class FirstAuthtication(BaseAuthentication):

    def authenticate(self, request):
        pass

    def authenticate_header(self, request):
        pass


class Authtication(BaseAuthentication):

    def authenticate(self, request):
        token = request._request.GET.get('token')
        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用户认证失败')
        # 在restframework内部会将整个两个字段赋值给request,以供后续继续使用
        return (token_obj.user, token_obj)

    def authenticate_header(self, request):
        return 'Basic realm="api"'      # api信息加入请求头

 

 

2.其他认证   BasicAuthentication

 

十、rest framework之权限的基本使用

使用方法和自定义认证类非常类似

1.定义权限类

# 定义权限类
class MyPermisson(object):

    def has_permission(self, request, view):
        if request.user.user_type != 3:
            return False
        return True


class MyPermisson1(object):
    def has_permission(self, request, view):
        if request.user.user_type == 3:
            return False
        return True

2. 使用自定义类

class OrderView(APIView):
    """
    订单相关业务
    """
    permission_classes = [MyPermisson, ]

    def get(self, request, *args, **kwargs):
        ret = {'code': 1000, 'msg': None, 'data': None}
        # request.user --> token_obj.user
        # request.auth --> token_obj

 

 十一、 rest framework之权限源码流程

# 查看dispatch中检测权限的方法   
 def check_permissions(self, request):
        """
        Check if the request should be permitted.
        Raises an appropriate exception if the request is not permitted.
        """
        for permission in self.get_permissions():
            if not permission.has_permission(request, self):
                self.permission_denied(
                    request, message=getattr(permission, 'message', None)
                )

 

定义全局的权限检测类,并使用全局配置定义全局权限检测的类

class SVIPPermisson(object):
    message = '必须是SVIP才能访问'
    def has_permission(self, request, view):
        if request.user.user_type != 3:
            return False
        return True


#定义配置
REST_FRAMEWORK  =  {
    'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.auth.FirstAuthtication', 'api.utils.auth.Authtication'],
    'UNAUTHENTICATED_USER':None,  #request.user
    'UNAUTHENTICATED_TOKEN':None,  # request.auth
    'DEFAULT_PERMISSION_CLASSES':['api.utils.permission.SVIPPermisson']  # request.auth
}

  

 十二、rest framework之权限的内置类

django 内置的权限类在实际生成的环境不建议使用,但是建议继承,可以帮助规范自定义权限类的方法名称;

除了BasePermission之外,还有其他的类:

 

#实现代码:
from rest_framework.permissions import BasePermission class SVIPPermisson(BasePermission): message = '必须是SVIP才能访问' def has_permission(self, request, view): if request.user.user_type != 3: return False return True class MyPermisson1(BasePermission): def has_permission(self, request, view): if request.user.user_type == 3: return False return True

  

 十三、rest framework之访问频率控制基本实现

import time

VISIT_RECODE = {}   # 放在全局变量中,重启之后,就会变空,可以放到缓存中
from rest_framework.throttling import BaseThrottle
class VisitThrottle(object):
    """60秒内只能访问3次"""
    def __init__(self):
        self.histoy = None

    def allow_request(self, request, view):
        # 1.获取用户IP
        remote_addr = request.META.get('REMOTE_ADDR')
        print(remote_addr)
        ctime = time.time()
        if remote_addr not in VISIT_RECODE:
            VISIT_RECODE[remote_addr] = [ctime, ]
            return True
        history = VISIT_RECODE.get(remote_addr)
        self.histoy = history
        while history and history[-1] < ctime -60:    # 如果记录的时间戳超过60s以内,就删除;
            history.pop()

        if len(history) < 3:
            history.insert(0, ctime)
            return True
        # return True  # 表示可以继续访问
        # return  False  # 表示访问频率太高,被限制

    def wait(self):
        """还有等多少秒才能访问 return 10 等10S才能访问"""
        ctime = time.time()
        return 60 - (ctime - self.histoy[-1])


class AuthView(APIView):
    authentication_classes = []
    throttle_classes = [VisitThrottle,]

 

十四、rest framework之访问频率控制源码流程

源码流程和上述认证与权限源码流程类似,下面使用全局配置类控制访问频率:

将上述代码挪到目录api.utils.thottle.VisitThrottle

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.auth.FirstAuthtication', 'api.utils.auth.Authtication'],
    'UNAUTHENTICATED_USER': None,  # request.user
    'UNAUTHENTICATED_TOKEN': None,  # request.auth
    'DEFAULT_PERMISSION_CLASSES':['api.utils.permission.SVIPPermisson'],  # request.auth
    'DEFAULT_THROTTLE_CLASSES': ['api.utils.thottle.VisitThrottle']   # 全局生效
}

 

十五、rest framework之基于内置类实现访问频率控制

实际上,内置的访问频率类已经实现了上述的方法,可以通过配置来自定义访问频率,VISIT_RECODE 是放置在缓存中的:

from rest_framework.throttling import BaseThrottle, SimpleRateThrottle

class VisitThrottle(SimpleRateThrottle):
    scope = 'visit'

    def get_cache_key(self, request, view):
        return self.get_ident(request)    # 获取用户IP


class UserThrottle(SimpleRateThrottle):
    scope = 'user'

    def get_cache_key(self, request, view):
        return request.user.username


###配置###
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.auth.FirstAuthtication', 'api.utils.auth.Authtication'],
    'UNAUTHENTICATED_USER': None,  # request.user
    'UNAUTHENTICATED_TOKEN': None,  # request.auth
    'DEFAULT_PERMISSION_CLASSES':['api.utils.permission.SVIPPermisson'],  # request.auth
    'DEFAULT_THROTTLE_CLASSES': ['api.utils.thottle.VisitThrottle'],   #默认为对匿名用户做限制
    'DEFAULT_THROTTLE_RATES': {
        'visit': '3/m',     # 一分钟访问3次
        'user': '10/m'
    }
}



###同时对登录用户做限制#####
from api.utils.thottle import UserThrottle
class OrderView(APIView):
    """
    订单相关业务
    """
    # permission_classes = [SVIPPermisson, ]
    throttle_classes = [UserThrottle, ]        # 只有用户登录才可以查看订单,使用另外一个访问频率限制类;

 

 十六、版本

1、在url中通过GET传参:

使用自定义的类解析版本

class ParamVersion(object):
    def determine_version(self, request, *args, **kwargs):
        version = request.query_params.get('version')
        return version


class UserView(APIView):
    versioning_class = ParamVersion
    permission_classes = []
    authentication_classes = []
    throttle_classes = []
    def get(self, request, *args, **kwargs):
        print(request.version)
        return HttpResponse('用户列表')

#get请求如下: http://127.0.0.1:8080/api/users/?version=v3

 

使用内置的类解析版本参数,还可以通过配置定义默认的版本以及允许的版本:

from rest_framework.versioning import QueryParameterVersioning,

class ParamVersion(object):
    def determine_version(self, request, *args, **kwargs):
        version = request.query_params.get('version')
        return version


class UserView(APIView):
    versioning_class = QueryParameterVersioning
    permission_classes = []
    authentication_classes = []
    throttle_classes = []
    def get(self, request, *args, **kwargs):
        print(request.version)
        return HttpResponse('用户列表')

#####settings#####
REST_FRAMEWORK = {
    'DEFAULT_VERSION' : 'v1',   # 默认的版本
    'ALLOWED_VERSIONS' : ['v1', 'v2'],    # 允许请求的版本
    'VERSION_PARAM': 'version',         # 版本的参数的key
}

 

2、在URL中传参(推荐使用): 版本在使用的时候,无需自定义,使用下面的方式就可以实现了;

urlpatterns = [
    url(r'^(?P<version>[v1|v2]+)/users/$', views.UserView.as_view()),
]


from rest_framework.versioning import QueryParameterVersioning, URLPathVersioning
class UserView(APIView):
    versioning_class = URLPathVersioning   # 除了在这儿设置之外,还可以在配置中设置
    permission_classes = []
    authentication_classes = []
    throttle_classes = []
    def get(self, request, *args, **kwargs):
        print(request.version)
        return HttpResponse('用户列表')


###settings.py######在配置中设置
REST_FRAMEWORK = {
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
    'DEFAULT_VERSION' : 'v1',
    'ALLOWED_VERSIONS' : ['v1', 'v2'],
    'VERSION_PARAM': 'version'
}

 

十七、rest framework框架之版本源码

# 可以在视图中反向解析URL

from django.urls import reverse
class UserView(APIView):
    # versioning_class = ParamVersion
    # versioning_class = URLPathVersioning
    permission_classes = []
    authentication_classes = []
    throttle_classes = []
    def get(self, request, *args, **kwargs):
        # 1.获取版本
        print(request.version)
        # 2.获取处理版本的对象
        print(request.versioning_scheme)

        # 3.反向生成URL(REST FRAMEWORK)
        url1 = request.versioning_scheme.reverse(viewname='uuu', request=request)
        print(url1)

        # 4.反向生成URL
        url2 = reverse(viewname='uuu', kwargs={'version': 2})
        print(url2)
        return HttpResponse('用户列表')


###打印结果
<rest_framework.versioning.URLPathVersioning object at 0x04335D50>
http://127.0.0.1:8080/api/v2/users/
/api/2/users/

 

十八、解析器

1.解析器预备知识(post提交的数据,会保存在request.body中,转换为QueryDict才能被request.post获取到)

#点击查看源码,
from  django.core.handlers.wsgi import  WSGIRequest
        elif self.content_type == 'application/x-www-form-urlencoded':
            self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict()   #


#如果想通过request.POST获取到post提交的数据,那么必须满足如下两个条件:
django:request.POST/ request.body
			1. 请求头要求:
				Content-Type: application/x-www-form-urlencoded
				PS: 如果请求头中的 Content-Type: application/x-www-form-urlencoded,request.POST中才有值(去request.body中解析数据)。
			2. 数据格式要求:
				  name=charles&age=18&gender=男
# 如果不满足上述条件,那么就必须使用request.body将字节转换为str,然后再做解析:		
			如:
				a. form表单提交,请求头和数据都满足上述条件:
					<form method...>
						input...
						
					</form>
					
				b. ajax提交
					$.ajax({
						url:...
						type:POST,
						data:{name:alex,age=18} # 内部转化 name=alex&age=18&gender=男
					})
					
					情况一:   #数据满足,请求头不满足
						$.ajax({
							url:...
							type:POST,
							headers:{'Content-Type':"application/json"}
							data:{name:alex,age=18} # 内部转化 name=alex&age=18&gender=男
						})
						# body有值;POST无
					情况二:# 数据和请求头都不满足
						$.ajax({
							url:...
							type:POST,
							headers:{'Content-Type':"application/json"}
							data:JSON.stringfy({name:alex,age=18}) # {name:alex,age:18...}
						})
						# body有值;POST无
						# json.loads(request.body)
					

 

# rest framework 解析器

#JSONParser支持解析请求头为application/json的数据
#FormParser  支持解析请求头为content-type:application/x-www-form-urlencoded的数据


from rest_framework.parsers import JSONParser,FormParser
class ParserView(APIView):
    # parser_classes = [JSONParser,FormParser,]   #查看请求头,自动匹配解析器
    """
    JSONParser:表示只能解析content-type:application/json头
    JSONParser:表示只能解析content-type:application/x-www-form-urlencoded头
    """

    def post(self, request, *args, **kwargs):
        """
        允许用户发送JSON格式数据
            a. content-type: application/json
            b. {'name':'alex',age:18}
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        """
        1. 获取用户请求
        2. 获取用户请求体
        3. 根据用户请求头 和 parser_classes = [JSONParser,FormParser,] 中支持的请求头进行比较
        4. JSONParser对象去请求体
        5. request.data
        """
        print(request.data)   # 解析后的数据

        return HttpResponse('ParserView')

  

 

 

 通过request.data可以看到解析器的源码,分析得到,解析器可以通过配置定义全局的解析器:

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

使用非默认的解析器使用配置如下:
class ParserView(APIView):
    parser_classes = [JSONParser,FormParser,]   # 自己的视图类中使用的解析器
    """

#除了之外,还有如下的解析器:
class FormParser(BaseParser):
    """
    Parser for form data.
    """
    media_type = 'application/x-www-form-urlencoded'

    def parse(self, stream, media_type=None, parser_context=None):
        """
        Parses the incoming bytestream as a URL encoded form,
        and returns the resulting QueryDict.
        """
        parser_context = parser_context or {}
        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
        data = QueryDict(stream.read(), encoding=encoding)
        return data


class MultiPartParser(BaseParser):
    """
    Parser for multipart form data, which may include file data.
    """
    media_type = 'multipart/form-data'

    def parse(self, stream, media_type=None, parser_context=None):
        """
        Parses the incoming bytestream as a multipart encoded form,
        and returns a DataAndFiles object.

        `.data` will be a `QueryDict` containing all the form parameters.
        `.files` will be a `QueryDict` containing all the form files.
        """
        parser_context = parser_context or {}
        request = parser_context['request']
        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
        meta = request.META.copy()
        meta['CONTENT_TYPE'] = media_type
        upload_handlers = request.upload_handlers

        try:
            parser = DjangoMultiPartParser(meta, stream, upload_handlers, encoding)
            data, files = parser.parse()
            return DataAndFiles(data, files)
        except MultiPartParserError as exc:
            raise ParseError('Multipart form parse error - %s' % six.text_type(exc))


class FileUploadParser(BaseParser):
    """
    Parser for file upload data.
    """
    media_type = '*/*'
    errors = {
        'unhandled': 'FileUpload parse error - none of upload handlers can handle the stream',
        'no_filename': 'Missing filename. Request should include a Content-Disposition header with a filename parameter.',
    }

    def parse(self, stream, media_type=None, parser_context=None):
        """
        Treats the incoming bytestream as a raw file upload and returns
        a `DataAndFiles` object.

        `.data` will be None (we expect request body to be a file content).
        `.files` will be a `QueryDict` containing one 'file' element.
        """
        parser_context = parser_context or {}
        request = parser_context['request']
        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
        meta = request.META
        upload_handlers = request.upload_handlers
        filename = self.get_filename(stream, media_type, parser_context)

        if not filename:
            raise ParseError(self.errors['no_filename'])

        # Note that this code is extracted from Django's handling of
        # file uploads in MultiPartParser.
        content_type = meta.get('HTTP_CONTENT_TYPE',
                                meta.get('CONTENT_TYPE', ''))
        try:
            content_length = int(meta.get('HTTP_CONTENT_LENGTH',
                                          meta.get('CONTENT_LENGTH', 0)))
        except (ValueError, TypeError):
            content_length = None

        # See if the handler will want to take care of the parsing.
        for handler in upload_handlers:
            result = handler.handle_raw_input(stream,
                                              meta,
                                              content_length,
                                              None,
                                              encoding)
            if result is not None:
                return DataAndFiles({}, {'file': result[1]})

        # This is the standard case.
        possible_sizes = [x.chunk_size for x in upload_handlers if x.chunk_size]
        chunk_size = min([2 ** 31 - 4] + possible_sizes)
        chunks = ChunkIter(stream, chunk_size)
        counters = [0] * len(upload_handlers)

        for index, handler in enumerate(upload_handlers):
            try:
                handler.new_file(None, filename, content_type,
                                 content_length, encoding)
            except StopFutureHandlers:
                upload_handlers = upload_handlers[:index + 1]
                break

        for chunk in chunks:
            for index, handler in enumerate(upload_handlers):
                chunk_length = len(chunk)
                chunk = handler.receive_data_chunk(chunk, counters[index])
                counters[index] += chunk_length
                if chunk is None:
                    break

        for index, handler in enumerate(upload_handlers):
            file_obj = handler.file_complete(counters[index])
            if file_obj is not None:
                return DataAndFiles({}, {'file': file_obj})

        raise ParseError(self.errors['unhandled'])

    def get_filename(self, stream, media_type, parser_context):
        """
        Detects the uploaded file name. First searches a 'filename' url kwarg.
        Then tries to parse Content-Disposition header.
        """
        try:
            return parser_context['kwargs']['filename']
        except KeyError:
            pass

        try:
            meta = parser_context['request'].META
            disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8'))
            filename_parm = disposition[1]
            if 'filename*' in filename_parm:
                return self.get_encoded_filename(filename_parm)
            return force_text(filename_parm['filename'])
        except (AttributeError, KeyError, ValueError):
            pass

    def get_encoded_filename(self, filename_parm):
        """
        Handle encoded filenames per RFC6266. See also:
        http://tools.ietf.org/html/rfc2231#section-4
        """
        encoded_filename = force_text(filename_parm['filename*'])
        try:
            charset, lang, filename = encoded_filename.split('\'', 2)
            filename = urlparse.unquote(filename)
        except (ValueError, LookupError):
            filename = force_text(filename_parm['filename'])
        return filename

 

引申内容如下:

1. http 状态码
2. http请求方法
3. http 请求头

 

十九、 rest framework框架之序列化

数据库表结构如下:

class UserGroup(models.Model):
    title  = models.CharField(max_length=32)

class UserInfo(models.Model):
    user_type_choices = (
        (1,'普通用户'),
        (2,'VIP'),
        (3,'SVIP'),
    )
    user_type = models.IntegerField(choices=user_type_choices)
    group = models.ForeignKey('UserGroup')

    username = models.CharField(max_length=32, unique=True)
    password = models.CharField(max_length=64)

    roles = models.ManyToManyField('Role')

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


class Role(models.Model):
    title  = models.CharField(max_length=32)

  

1、序列化基本使用

a. django的序列化

如果是django的QuerySet对象,直接使用json.dumps进行处理,是会报错的,使用django的序列化工具不太好用,一版我们使用values/value_list转换为列表之后,再进行序列化:

import json
class RoleView(APIView):
    def get(self, request, *args, **kwargs):
        roles = models.Role.objects.all().values('id', 'title')
        roles = list(roles)
        ret = json.dumps(roles, ensure_ascii=False)   # ensure_ascii=False 表示如果有中文,不是输出字节码,而是中文字符
        return HttpResponse(ret)

 

b.使用rest framework的序列化工具

b1.

from rest_framework import serializers

class RolesSerializer(serializers.Serializer):   # 下面的字段必须是数据库的字段
    id = serializers.IntegerField()
    title = serializers.CharField()



class RoleView(APIView):
    def get(self, request, *args, **kwargs):
        roles = models.Role.objects.all()
        ser = RolesSerializer(instance=roles, many=True)   # 如果QuerySet不是一个对象,使用many=True,如果是一个对象,如.first()/.last(),那么使用many=False
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)  

b2.

上述序列化的是简单的CharField字典,如果字段是choice/ForeignKey/ManyToMany,那么如何序列化呢?

class UserInfoSerializer(serializers.Serializer):
    xxxx = serializers.CharField(source='user_type')  # 显示choice的id
    ooo = serializers.CharField(source='get_user_type_display')   # 显示choice的value
    username = serializers.CharField()
    password = serializers.CharField()
    gp = serializers.CharField(source='group.title')    # source指定序列化的字段

    # rls = serializers.CharField(source='roles.all')
    rls  = serializers.SerializerMethodField()     # ManyToMany 可以指定方法,由方法返回需要被序列化展示的内容

    def get_rls(self, row):     # 方法名为get_名称(这里是rls)
        roles_obj_list = row.roles.all()
        ret = []
        for item in roles_obj_list:
            ret.append({'id': item.id, 'title': item.title})
        return ret

class UserInfoView(APIView):

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

        users = models.UserInfo.objects.all()
        ser = UserInfoSerializer(instance=users, many=True)

        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

参考: http://www.cnblogs.com/wupeiqi/articles/7805382.html

 

 b3.

使用rest framework   ModelSerializer也可以使用上述的序列化的功能,但是更省事:

class UserInfoSerializer(serializers.ModelSerializer):
    ooo = serializers.CharField(source='get_user_type_display')
    rls = serializers.SerializerMethodField()

    class Meta:
        model = models.UserInfo
        # fields = "__all__"   # 显示所有字段,但是外键部分只显示ID
        fields = ['id', 'username', 'password', 'ooo', 'rls', 'group']

    def get_rls(self, row):
        roles_obj_list = row.roles.all()
        ret = []
        for item in roles_obj_list:
            ret.append({'id': item.id, 'title': item.title})
        return ret

在序列化的时候,上面的CharField可以使用自定义的类(一般不使用):

class MyField(serializers.CharField):
    pass

class UserInfoSerializer(serializers.ModelSerializer):
    ooo = serializers.CharField(source='get_user_type_display')
    rls = serializers.SerializerMethodField()
    x1 = MyField(source='username')

    class Meta:
        model = models.UserInfo
        # fields = "__all__"   # 显示所有字段,但是外键部分只显示ID
        fields = ['id', 'username', 'password', 'ooo', 'rls', 'group', 'x1']

    def get_rls(self, row):
        roles_obj_list = row.roles.all()
        ret = []
        for item in roles_obj_list:
            ret.append({'id': item.id, 'title': item.title})
        return ret




class MyField(serializers.CharField):

    def to_representation(self, value):
        print(value)
        return 'xxxxx'     # 返回值, 这里将返回显示的值写死了,而不是从数据库中去获取

 

b4.

使用depth, 可以自动序列化连表

class UserInfoSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.UserInfo
        # fields = "__all__"   # 显示所有字段,但是外键部分只显示ID
        fields = ['id', 'username', 'password', 'roles', 'group']
        depth = 1   # 建议值为0~3,默认为0

 

b5.

自动生成链接

urls.py

urlpatterns = [
    url(r'^(?P<version>[v1|v2]+)/userinfo/$', views.UserInfoView.as_view()),
    url(r'^(?P<version>[v1|v2]+)/usergroup/(?P<xxx>\d+)$', views.UserGroupView.as_view(), name='gp'),
]

 

views.py

class UserInfoSerializer(serializers.ModelSerializer):
    group = serializers.HyperlinkedIdentityField(view_name='gp', lookup_field='group_id', lookup_url_kwarg='xxx')    #lookup_field 从数据库取值
    class Meta:
        model = models.UserInfo
        fields = ['id', 'username', 'password', 'roles', 'group']
        depth = 0



class UserInfoView(APIView):

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

        users = models.UserInfo.objects.all()
        ser = UserInfoSerializer(instance=users, many=True, context={'request': request})

        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)



class UserGroupSerializer(serializers.ModelSerializer): 

    class Meta:
        model = models.UserGroup
        fields = "__all__"   # 显示所有字段,但是外键部分只显示ID
        # fields = ['id', 'username', 'password', 'roles', 'group']
        # depth = 0

class UserGroupView(APIView):

    def get(self, request, *args, **kwargs):
        pk = kwargs.get('xxx')
        obj = models.UserGroup.objects.filter(pk=pk).first()
        print(obj)
        ser = UserGroupSerializer(instance=obj, many=False)

        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret) 

 

 

 

引申知识点: 如何判断一个变量是否是函数

import types
def func(arg):
    # if callable(arg):
    if isinstance(arg, types.FunctionType):
        print(arg())
    else:
        print(arg)

func(123)
func(lambda :"666")

 

二十、验证用户请求数据

 

这里我们使用的解析器是: ['rest_framework.parsers.JSONParser', 'rest_framework.parsers.FormParser']

所以提交的验证数据为:

 

class XXValidator(object):
    def __init__(self, base):
        self.base = base

    def __call__(self, value,  *args, **kwargs):   # 这里的value是用户提交的数据
        if not value.startswith(self.base):
            message = '标题必须以 %s 开头' %self.base
            raise serializers.ValidationError(message)

    def set_context(self, seralizer_field):
        pass
class GroupSerializer(serializers.Serializer):
    title = serializers.CharField(error_messages={'required': '标题不能为空'}, validators=[XXValidator('老男人')])    # validator表示自定义验证规则, 

class GroupView(APIView): def post(self, request, *args, **kwargs): ser = GroupSerializer(data=request.data) # request.data 获取请求体中的数据 if ser.is_valid(): print(ser.validated_data) else: print(ser.errors) # 输出 {'title': ['标题必须以 老男人 开头']}
        return HttpResponse('提交数据')

 验证钩子

class GroupSerializer(serializers.Serializer):
    title = serializers.CharField(error_messages={'required': '标题不能为空'}, validators=[XXValidator('老男人')])

    def validate_title(self, value):   # 这里的value是验证的消息,是ser.validated_data,数据通过验证,会走这个钩子函数
        from rest_framework import exceptions
        # raise exceptions.ValidationError('哈哈哈')
        print(value, "xxxxx")
        return value

 

二十一、渲染器

from api.utils.serializers.pager import PagerSerializer
from rest_framework.response import Response
class Pager1View(APIView):

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

        roles = models.Role.objects.all()
        ser = PagerSerializer(instance=roles, many=True)
        return Response(ser.data)    # 使用渲染器显示接口数据

为什么会展示上面的内容呢?

from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer   # 默认使用的渲染器是这里的全部的渲染器

		class TestView(APIView):
			# renderer_classes = [JSONRenderer,BrowsableAPIRenderer]    # 可以在这里定义该视图使用的渲染器,
			def get(self, request, *args, **kwargs):
				# 获取所有数据
				roles = models.Role.objects.all()

				# 创建分页对象
				# pg = CursorPagination()
				pg = MyCursorPagination()

				# 在数据库中获取分页的数据
				pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self)

				# 对数据进行序列化
				ser = PagerSerialiser(instance=pager_roles, many=True)

				return Response(ser.data)


# 也可以使用全局的配置,配置默认的渲染器
		REST_FRAMEWORK = {
			"DEFAULT_RENDERER_CLASSES":[
				'rest_framework.renderers.JSONRenderer',
				'rest_framework.renderers.BrowsableAPIRenderer',
			]
		}

 

当然,我们可以继承上面的渲染器,然后自定制自己的显示页面等内容:

class BrowsableAPIRenderer(BaseRenderer):
    """
    HTML renderer used to self-document the API.
    """
    media_type = 'text/html'
    format = 'api'
    template = 'rest_framework/api.html'     # 这里页面的内容,我们可以进行在子类中替换,哈哈哈哈
    filter_template = 'rest_framework/filters/base.html'
    code_style = 'emacs'
    charset = 'utf-8'
    form_renderer_class = HTMLFormRenderer

  

二十二、分页器

#自定义序列化的类

#pager.py
from rest_framework import serializers
from  api import models

class PagerSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = "__all__"

22.1

#分页

REST_FRAMEWORK = {
    'PAGE_SIZE': 2,     # 定义每页分页的大小
}


class Pager1View(APIView):

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

        #获取所有数据
        roles = models.Role.objects.all()

        # 创建分页对象
        pg = PageNumberPagination()

        # 在数据库中获取分页的数据
        pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self)   # 返回的是分页的对象

        # 对分页的数据进行序列化
        ser = PagerSerializer(instance=pager_roles, many=True)
        print(pager_roles)
        return Response(ser.data)

 

22.2

 除此之外,我们还可以自定义分页的大小,通过自定义的类来实现:

class MyPageNumberPagination(PageNumberPagination):
    page_size = 2
    page_query_param = 'page'    # 查询分页时使用的参数
    page_size_query_param = 'size'  # 是否可以自定义查询分页的大小

    max_page_size = 5  # 每个分页的最大值


class Pager1View(APIView):

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

        #获取所有数据
        roles = models.Role.objects.all()

        # 创建分页对象
        pg = MyPageNumberPagination()   # 自定义的分页类

        # 在数据库中获取分页的数据
        pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self)

        # 对分页的数据进行序列化
        ser = PagerSerializer(instance=pager_roles, many=True)
        print(pager_roles)
        return Response(ser.data)

22.3 

如果返回为:

        ser = PagerSerializer(instance=pager_roles, many=True)
        print(pager_roles)
        # return Response(ser.data)
        return pg.get_paginated_response(ser.data)

则显示如下的内容:

 

 22.4

另外使用LimitOffsetPagination也可以实现上述功能

from rest_framework.pagination import LimitOffsetPagination

class LimitOffsetPagination(BasePagination): """ A limit/offset based style. For example: http://api.example.org/accounts/?limit=100 http://api.example.org/accounts/?offset=400&limit=100 # offset 是从0开始的 """ default_limit = api_settings.PAGE_SIZE limit_query_param = 'limit' limit_query_description = _('Number of results to return per page.') offset_query_param = 'offset' offset_query_description = _('The initial index from which to return the results.') max_limit = None template = 'rest_framework/pagination/numbers.html'

 

22.5 加密分页

from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination

class MyPageNumberPagination(CursorPagination):
    cursor_query_param = 'cursor'    # 查询页的ID
    page_size = 2
    ordering = '-id'     # 排序
    page_size_query_param = None
    max_page_size = None

 

二十三、 rest framework之视图

23.1  GenericAPIView

从源码看,GenericAPIView是继承了APIView, 实现的功能和APIView没有任何区别,做了解即可:

继承的顺序是View-->APIView--> GenericView

class APIView(View):

    # The following policies ma


class GenericAPIView(views.APIView):
    """
    Base class for all other generic views.

实现代码如下:

from rest_framework.pagination import PageNumberPagination
from api.utils.serializers.pager import PagerSerializer
from rest_framework.generics import GenericAPIView
class View1View(GenericAPIView):
    queryset = models.Role.objects.all()
    serializer_class = PagerSerializer
    pagination_class = PageNumberPagination
    def get(self, request, *args, **kwargs):
        # 获取数据
        roles = self.get_queryset()   # models.Role.objects.all()
        pager_roles = self.paginate_queryset(roles)

        # 序列化
        ser = self.get_serializer(instance=pager_roles, many=True)
        return Response(ser.data)

23.2 GenericViewSet

与上面的GenericAPIView不同的是,重写了as_view()方法;

# 继承了ViewSetMixin和GenericAPIView两个类,ViewSetMixin中重写了as_view方法;


class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
    """
    The GenericViewSet class does not provide any actions by default,
    but does include the base set of generic view behavior, such as
    the `get_object` and `get_queryset` methods.
    """
    pass



class ViewSetMixin(object):
    """
    This is the magic.

    Overrides `.as_view()` so that it takes an `actions` keyword that performs
    the binding of HTTP methods to actions on the Resource.

    For example, to create a concrete view binding the 'GET' and 'POST' methods
    to the 'list' and 'create' actions...

    view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
    """

    @classonlymethod
    def as_view(cls, actions=None, **initkwargs):
        """
        Because of the way class based views create a closure around the
        instantiated view, we need to totally reimplement `.as_view`,
        and slightly modify the view function that is created and returned.
        """
        # The suffix initkwarg is reserved for displaying the viewset type.
        # eg. 'List' or 'Instance'.
        cls.suffix = None

        # Setting a basename allows a view to reverse its action urls. This
        # value is provided by the router through the initkwargs.
        cls.basename = None

 

实现代码:

urls.py

url(r'^(?P<version>[v1|v2]+)/v1/$', views.View1View.as_view({'get': 'list'})),   # method为GET时,去寻找视图类中的list方法;

views.py

from rest_framework.viewsets import GenericViewSet
class View1View(GenericViewSet):
    queryset = models.Role.objects.all()
    serializer_class = PagerSerializer
    pagination_class = PageNumberPagination
    def list(self, request, *args, **kwargs):   # list方法必须要实现
        # 获取数据
        roles = self.get_queryset()   # models.Role.objects.all()
        pager_roles = self.paginate_queryset(roles)

        # 序列化
        ser = self.get_serializer(instance=pager_roles, many=True)
        return Response(ser.data) 

 

23.3 ModelViewSet

ModelViewSet 继承了多个类: 每个类实现了一个特定的方法,在实现的时候,无需在视图中实现这些方法,只需要在as_view中指定方法名就可以了

class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    """
    A viewset that provides default `create()`, `retrieve()`, `update()`,
    `partial_update()`, `destroy()` and `list()` actions.
    """
    pass

 

实现代码:

urls.py

 url(r'^(?P<version>[v1|v2]+)/v1/$', views.View1View.as_view({'get': 'list', 'post':'create'})),    # 获取列表和创建数据无需传递id
    url(r'^(?P<version>[v1|v2]+)/v1/(?P<pk>\d+)$', views.View1View.as_view({'get': 'retrieve', 'delete': 'destroy', 'put': 'update', 'patch': 'partial_update'})),    # 因为update、delete等操作,需要传递id,所以在实现的时候,需要两个路由;

views.py

from rest_framework.viewsets import ModelViewSet
class View1View(ModelViewSet):
    queryset = models.Role.objects.all()
    serializer_class = PagerSerializer
    pagination_class = PageNumberPagination

 当然了,也可以只继承mixins中的任意一个或者多个类;

总结使用:

			a. 增删改查  使用ModelViewSet
			b. 增删     使用CreateModelMixin,DestroyModelMixin  GenericViewSet
			c. 复杂逻辑  使用GenericViewSet 或 APIView 

  

二十四、路由

一般一个视图,我们最多写四个url就够了

# http://127.0.0.1:8000/api/v1/v1/?format=json
url(r'^(?P<version>[v1|v2]+)/v1/$', views.View1View.as_view({'get': 'list','post':'create'})),
# http://127.0.0.1:8000/api/v1/v1.json
url(r'^(?P<version>[v1|v2]+)/v1\.(?P<format>\w+)$', views.View1View.as_view({'get': 'list','post':'create'})),
url(r'^(?P<version>[v1|v2]+)/v1/(?P<pk>\d+)/$', views.View1View.as_view({'get': 'retrieve','delete':'destroy','put':'update','patch':'partial_update'})),
url(r'^(?P<version>[v1|v2]+)/v1/(?P<pk>\d+)\.(?P<format>\w+)$', views.View1View.as_view({'get': 'retrieve','delete':'destroy','put':'update','patch':'partial_update'})),

  

如果嫌麻烦,可以使用全自动路由:

from api import views
from rest_framework import routers

router = routers.DefaultRouter()
router.register(r'xxxxx', views.View1View)
router.register(r'rt', views.View1View)


urlpatterns = [
    url(r'^(?P<version>[v1|v2]+)/', include(router.urls)),
]

  

如果写单个url就自己写,如果写全部的增删改查就自动生成;

 

二十五、content-type

在做前后端分离的时候,涉及跨域的问题,解决办法:

1. jsonp

2. cors:  - 将这个响应头放在中间件中进行实现;

content-type: 是django内置的组件,帮助开发者做连表操作。

 

from django.db import models

# Create your models here.

from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation

class Course(models.Model):
    """
    普通课程
    """
    title = models.CharField(max_length=12)
    # 仅用于反向查找
    price_policy_list = GenericRelation("PricePlicy")

class DegreeCourse(models.Model):
    """
    学位课程
    """
    title = models.CharField(max_length=32)
    price_policy_list = GenericRelation("PricePlicy")
    
    
class PricePlicy(models.Model):
    """
    价格策略
    """
    price = models.IntegerField()
    period = models.IntegerField()

    content_type = models.ForeignKey(ContentType, verbose_name='关联的表的名称')
    object_id = models.IntegerField(verbose_name='关联的表中的数据行的ID')
    # 快速实现content_type 操作
    content_object = GenericForeignKey('content_type', 'object_id')   # 会自动找到obj 对象的id和关联的表中的数据行的ID,并进行赋值

obj = DegreeCourse.objects.filter(title='python').first()
PricePlicy.objects.create(price=9.9, period=30, content_object=obj)

 

视图函数

from app01 import models

def test(request):
    obj1 = models.DegreeCourse.objects.filter(title='python').first()
    models.PricePlicy.objects.create(price=9.9, period=30, content_object=obj1)

    obj1 = models.Course.objects.filter(title='rest framework').first()
    models.PricePlicy.objects.create(price=9.9, period=30, content_object=obj1)
    course = models.Course.objects.filter(id=1).first()
    price_policys = course.price_policy_list.all()
    print(price_policys)
    return HttpResponse('....')

 

posted on 2018-05-05 23:16  阿里山QQ  阅读(364)  评论(0编辑  收藏  举报