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