django restfulwork 源碼剖析

概要:
  1.restful 規範(建議);
  2. django rest framework框架
內容回顧:
  1.開發模式;
    - 普通開發模式(先後端放在一塊兒寫)
    - 先後端分離
      好處: 後端一套,前端使用app,pc等;
  2. 後端開發
    爲前端提供URL(API的開發)
    注:永遠返回Httpresponse
  3. django Fbv 和Cbv
 
1、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必須這麼寫:html

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

 

使用postman進行測試:前端

 

查看源碼預備知識:封裝python

#!/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,而後經過反射,執行子類中對應的方法;ajax

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-->Viewdjango

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

  

 2、Django中間件json

1. 中間件執行順序(中間件最多能夠實現5個方法)後端

  正常順序:  執行全部process_request-->路由匹配-->執行全部process_view-->執行視圖函數-->process_responseapi

       若是有報錯: 執行全部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')

  

3、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

 

4、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

  - 如何驗證(基於數據庫實現用戶認證)

  -源碼流程(面向對象回顧流程)

 

5、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發送請求進行驗證

 

6、 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)

 

7、rest framework之認證基本流程源碼分析

一、請求進來以後,會先執行dispatch()方法;

class OrderView(APIView):
    """
    訂單相關業務
    """
    authentication_classes = [Authtication, ]

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

二、先對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
                                                                                                                                    

三、認證

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

 簡要流程圖以下:

 

 8、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)

 

二、在用戶沒有登陸(匿名用戶的時候)

    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   
}

  

9、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

 

10、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

 

 11、 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
}

  

 12、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

  

 十3、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,]

 

十4、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']   # 全局生效
}

 

十5、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, ]        # 只有用戶登陸才能夠查看訂單,使用另一個訪問頻率限制類;

 

 十6、版本

一、在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
}

 

二、在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'
}

 

十7、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/

 

十8、解析器

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 請求頭

 

十9、 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)

  

一、序列化基本使用

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

 

二10、驗證用戶請求數據

 

這裏咱們使用的解析器是: ['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

 

二11、渲染器

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

  

二12、分頁器

#自定義序列化的類

#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

 

二十3、 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 

  

二十4、路由

通常一個視圖,咱們最多寫四個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就本身寫,若是寫所有的增刪改查就自動生成;

 

二十5、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('....')
相關文章
相關標籤/搜索