前后端分離后,后端不能寫前端的代碼,那么如何發post請求呢,這時候就可以用postman插件
局部避免CSRF的方式
后端不能寫前端代碼那么如何來避免CSRF呢,不要說注釋掉中間件方法太lower了
對於FBV我們這樣做:
from django.views.decorators.csrf import csrf_exempt @csrf_exempt def add(request): return HttpResponse("我們添加好了")
對於CBV的話我們這樣做
from django.shortcuts import render,HttpResponse from django.views import View from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt # Create your views here. @method_decorator(csrf_exempt,name="dispatch") #這樣就給類下面的所有的方法添加了CSRF保護 class IndexView(View): def get(self,request): return HttpResponse("get.....") def post(self,request): return HttpResponse("post.....")
與csrf_exempt相反作用的是csrf_protect(即使注釋掉中間件也能用)
rest-framework
rest-framework官方文檔
django-rest-framework,是一套基於Django的REST框架.
安裝rest-framework插件
pip install djangorestframework
1.全部信息 users/ ----查看所有數據 get users/ :返回所有的用戶的json數據 ----提交數據 post users/ :返回添加數據的json數據
2.具體對象 users/2 ----查看 get users/2 :返回具體查看的用戶的json數據 ----刪除 delete users/2 :返回空文檔 ----編輯 put/patch users/2:返回的編輯后的json數據包
版本控制
版本控制有利於我們根據客戶端的版本不同做出不同的處理,比如微信的版本不同
全局控制
urls.py
from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^api/(?P<version>\w+)/', include('weixin.urls')),#允許版本v1和版本v2共存 ]
weixin.urls.py
urlpatterns = [ url(r'^drf/$', views.DrfView.as_view()), ]
settings.py
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning'#從URL路徑中取版本
'DEFAULT_VERSION': 'v1', # 默認版本
'ALLOWED_VERSIONS': ['v1', 'v2'], # 允許的版本 '
VERSION_PARAM': 'version' # URL中獲取值的key
}
局部控制
views
from rest_framework.versioning import QueryParameterVersioning,URLPathVersioning class TextView(APIView): versioning_class =URLPathVersioning # 版本控制 def get(self,request,*args,**kwargs): vertion = request.vertion return Httpresponse("成功")
版本傳參有兩種方式:第一種在url中的?后邊傳版本,使用QueryParameterVersioning這個類
第二種在url中傳版本:使用URLPathVersioning 這個類
序列化
由於queryset不能被json序列化,所以我們要整理下數據,因為通常json后的數據格式是這樣的,列表中套字典
[ { "title": "python", "price": 123 }, { "title": "php", "price": 233 }, { "title": "GO", "price": 33 } ]
方式一list強轉
注意: json.dumps(data,ensure_ascii=False) 可以解決頁面上中文亂碼的問題.
# Create your views here. from django.views import View from api_demo.models import * import json #queryset 不能被json序列化 class BookView(View): def get(self,request,*args,**kwargs): booklist=list(Book.objects.all().values("title","price")) return HttpResponse(json.dumps(booklist))
方式二拼湊格式
from django.shortcuts import render,HttpResponse # Create your views here. from django.views import View from api_demo.models import * import json class BookView(View): def get(self,request,*args,**kwargs): booklist=Book.objects.all() temp=[] for book in booklist: temp.append({ "title":book.title, "price":book.price }) return HttpResponse(json.dumps(temp))
方式三:Django的序列化工具serializers
關於Django中的序列化主要應用在將數據庫中檢索的數據返回給客戶端用戶,特別的Ajax請求一般返回的為Json格式。
from django.shortcuts import render,HttpResponse from django.core import serializers #導入序列化 from django.views import View from api_demo.models import * class BookView(View): def get(self,request,*args,**kwargs): booklist=Book.objects.all() temp=serializers.serialize("json",booklist) return HttpResponse(temp)
這樣得到是所有字段的信息
結果:

[ { "model": "api_demo.book", "pk": 1, "fields": { "title": "python", "price": 123, "pub_date": null, "publish": 1, "authors": [ 1, 2 ] } }, { "model": "api_demo.book", "pk": 2, "fields": { "title": "php", "price": 233, "pub_date": null, "publish": 2, "authors": [ 1 ] } }, { "model": "api_demo.book", "pk": 3, "fields": { "title": "GO", "price": 33, "pub_date": null, "publish": 2, "authors": [ 1, 2 ] } } ]
以上三種的缺點: 盡管能把數據json序列化,但是不能json反序列化,這時候就出現了第四種方法
方式4.restframwork專門處理序列化的組件:serializers組件
from rest_framework.response import Response #引入restframework自己的Response from rest_framework.views import APIView #引入 APIView from rest_framework import serializers #用rest_framework自己的serializers from api_demo.models import * class Bookserializers(serializers.Serializer): """ 為book表建立序列化組件 """ title=serializers.CharField(max_length=32) price=serializers.IntegerField() class BookView(APIView):#注意這里使用的是APIView不是View,如果是View不能用restframe的序列化 def get(self,request,*args,**kwargs): booklist=Book.objects.all() temp=Bookserializers(booklist,many=True)#如果傳入的是多個值,由於queryset是多個對象的集合,many=True,默認False print(">>>>",temp) print("-------",temp.data) #調用靜態方法data,得到的是一種orderDict數據類型的數據 return Response(temp.data) #必須要用rest_framework的Response發送,因為還要對data數據進行處理,發送其中的data就可以
結果:
>>>> Bookserializers(<QuerySet [<Book: python>, <Book: php>, <Book: GO>]>, many=True): title = CharField(max_length=32) price = IntegerField() ------- [OrderedDict([('title', 'python'), ('price', 123)]), OrderedDict([('title', 'php'), ('price', 233)]), OrderedDict([('title', 'GO'), ('price', 33)])]
注意 :
1.對於一對多字段這樣設置
class Bookserializers(serializers.Serializer): title=serializers.CharField(max_length=32) price=serializers.IntegerField() publish_name=serializers.Charfield(source="publish.name") #這里可以指定顯示連表的的具體字段,如果不指定就顯示關聯表的主鍵值
2.對於多對多字段需要重新定義多對多字段
當連表是多對多,需要顯示連表中具體字段時可以這么操作
class Bookserializers(serializers.Serializer): title = serializers.CharField(max_length=32) price = serializers.IntegerField() pub_date = serializers.DateField() publish = serializers.CharField()#一對多 authors = serializers.SerializerMethodField() #對多對 def get_authors(self,obj): #函數名字必須為get_多對多字段形式,obj就是每個Book對象,源碼中規定的 temp=[] for author in obj.authors.all(): temp.append(author.name) #把多對多的名字顯示出來 return temp
3.序列化關於choices字段的處理
詳情見: https://www.cnblogs.com/sticker0726/articles/12895099.html
class Bookserializers(serializers.Serializer): title = serializers.CharField(max_length=32) price = serializers.IntegerField() pub_date = serializers.DateField() sex = serializers.CharField(source='get_sex_display') # get_含有choice的字段_display
方式4的加強版(ModelSerializer )
讓我們聯想一下Form和ModelFrom,這里也有一個像MOdelFrom一樣的類,ModelSerializer(繼承serializer) 比Serializer多封裝好了一層,直接自己生成的create和update.方法
from django.shortcuts import render,HttpResponse from rest_framework.response import Response from rest_framework.views import APIView from rest_framework import serializers from django.views import View from api_demo.models import * class Bookserializers(serializers.ModelSerializer): #導入ModelSerializer這個類 class Meta: model=Book fields="__all__" #對多對字段默認是主鍵值 class BookView(APIView): def get(self,request,*args,**kwargs): booklist=Book.objects.all() temp=Bookserializers(booklist,many=True)#如果傳入的是多個值,many=True,默認False return Response(temp.data) #發送其中的data就可以
效果:

[ { "id": 1, "authors": [ { "pk": 1, "name": "alex" }, { "pk": 2, "name": "yuan" } ], "title": "python", "price": 123, "pub_date": "2018-04-26", "publish": 1 }, { "id": 2, "authors": [ { "pk": 1, "name": "alex" }, { "pk": 2, "name": "yuan" } ], "title": "php", "price": 233, "pub_date": "2018-04-08", "publish": 2 }, { "id": 3, "authors": [ { "pk": 1, "name": "alex" }, { "pk": 2, "name": "yuan" } ], "title": "GO", "price": 33, "pub_date": "2018-04-05", "publish": 2 } ]
注意: 1.其中多對多字段顯示的是主鍵值,如果你需要顯示別的字段可以這樣做
class Bookserializers(serializers.ModelSerializer): #導入ModelSerializer這個類 class Meta: model=Book fields="__all__" #對多對字段默認是主鍵值 ####注意下邊的縮進問題 ###### authors=serializers.SerializerMethodField() #多對多的字段,覆蓋了上邊的__all__中的authors字段 def get_authors(self,obj): temp1=[] for author in Author.objects.all(): temp1.append({"pk":author.pk,"name":author.name}) return temp1
2.如果你想要顯示多對多字段外鍵的所有字段 用depth=1
class Bookserializers(serializers.ModelSerializer): #導入ModelSerializer這個類 class Meta: model=Book fields="__all__" #對多對字段默認是主鍵值 depth=1
效果:

[{ "id": 1, "title": "python", "price": 123, "pub_date": "2018-04-26", "publish": { "id": 1, "name": "北京出版社", "email": "123@qq.com" }, "authors": [ { "id": 1, "name": "alex", "age": 22 }, { "id": 2, "name": "yuan", "age": 36 } ] },]
對book表中所有對象進行查看添加
serializers_base.py
from rest_framework import serializers from api_demo.models import * class Bookserializers(serializers.ModelSerializer): #導入ModelSerializer這個類 class Meta: model=Book fields="__all__" #對多對字段默認是主鍵值 depth=1
urls.py
from django.conf.urls import url from django.contrib import admin from api_demo.views import BookView, urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^books/$', BookView.as_view()), ]
views.py
#對book表中的所有資源進行查看 class BookView(APIView): def get(self,request,*args,**kwargs): booklist=Book.objects.all() temp=Bookserializers(booklist,many=True)# 序列化操作,如果傳入的是多個值,many=True,默認False return Response(temp.data) #發送其中的data就可以 def post(self,request,*args,**kwargs): """ 添加函數 :param request: :param args: :param kwargs: :return: """ data=request.data #postman發過來的是json數據類型,必須在request.data中才能取出來。如果前端發過來的數據不是json,,如果你要取數據有三種方法:
方法一:request.data中,看源碼后端要用request.data來接收
方法二:request._request.POST
方法三:request。POST rest_framework又重新封裝了POST方法
bs=Bookserializers(data=data) #這里包括了反序列化,生成對象,但是沒有保存到數據庫中 if bs.is_valid():#校驗數據像modelForm一樣 bs.save() return Response(bs.data) else: return Response(bs.errors)
對單個對象查看,刪除,更新
urls.py
from django.conf.urls import url from django.contrib import admin from api_demo.views import BookView,BookdetailView urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^books/$', BookView.as_view()), url(r'^books/(?P<book_id>\d+)/$',BookdetailView.as_view(),name="books") ]
views.py
class BookdetailView(APIView): def get(self,request,book_id,*args,**kwargs): """ 對單個對象查 :param request: :param book_id: :param args: :param kwargs: :return: """ book_obj=Book.objects.filter(pk=book_id).first() if book_obj: #防止刪除的數據,在查詢時出現null的結果 temp=Bookserializers(book_obj,context = {'request': request}) #顯示url時,必須添加的內容 return Response(temp.data) else: return Response("你查詢的數據不存在") def put(self,request,book_id,*args,**kwargs): """ 對單個對象編輯 :param request: :param book_id: :param args: :param kwargs: :return: """ book_obj=Book.objects.filter(pk=book_id).first() temp=Bookserializers(data=request.data,instance=book_obj,context = {'request': request}) #data是前端要修改的數據,instance是要修改的對象,context if temp.is_valid(): temp.save() return Response(temp.data) else: return Response(temp.errors) def patch(self, request, book_id, *args, **kwargs): obj = Book.objects.filter(pk=book_id).first() temp = TextSerializer(data=request.data, instance=obj, many=False, partial=True) #partial設為True if temp.is_valid(): temp.save() Response(temp.data) else: Response(temp.errors) def delete(self,request,book_id,*args,**kwargs): """ 對單個對象進行刪除 :param request: :param book_id: :param args: :param kwargs: :return: """ book_id=Book.objects.filter(pk=book_id).delete() return Response("刪除成功")
超鏈接API:Hyperlinked
這個超鏈接是什么意思呢?
它指的是當你返回的結果中有一對多或多對多時,這時候,服務器會返回一條url,你點擊這個url就會直接看到具體的信息,
比如對於單個book對象來說,他返回的hyperlinked,點進去就會看到publish的id,title,email等信息
urls.py
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^books/$', BookView.as_view()), url(r'^publishes/$', PublishView.as_view()), url(r'^books/(?P<pk>\d+)/$',BookdetailView.as_view(),name="books"), url(r'^publishes/(?P<pk>\d+)/$',PublishdetailView.as_view(),name="publishes") ]
serializers_base.py
from rest_framework import serializers from api_demo.models import * class Bookserializers(serializers.ModelSerializer): # 導入ModelSerializer這個類 publish = serializers.HyperlinkedIdentityField( view_name='publishes', # publish具體對象對應的url別名 lookup_field="publish_id", # 該字段在表book中的字段名 lookup_url_kwarg="pk") # publish具體對象對應的url中參數的名字 class Meta: model = Book fields = "__all__" # 對多對字段默認是主鍵值 depth = 1
注意
這里你也要設計url.和序列化publish,否則超鏈接不會成功,代碼因為重復我沒有寫
出現問題:
`HyperlinkedIdentityField` requires the request in the serializer context. Add `context={'request': request}` when instantiating the serializer.
這里要求我們在所有調用Bookserializers類進行序列化對象時都要添加上`context={'request': request},否則就會報上邊的錯誤

class BookView(APIView): def get(self,request,*args,**kwargs): booklist=Book.objects.all() temp=Bookserializers(booklist,many=True,context = {'request': request})# 序列化操作,如果傳入的是多個值,many=True,默認False return Response(temp.data) #發送其中的data就可以 def post(self,request,*args,**kwargs): """ 添加函數 :param request: :param args: :param kwargs: :return: """ data=request.data #postman發過來的json,后端要用request.data來接收 bs=Bookserializers(data=data,context = {'request': request}) #這里包括了反序列化,生成對象,但是沒有保存到數據庫中 if bs.is_valid():#校驗數據 bs.save() return Response(bs.data) else: return Response(bs.errors) #對book表中的單個數據進行查看,刪除,編輯 class BookdetailView(APIView): def get(self,request,book_id,*args,**kwargs): """ 對單個對象查 :param request: :param book_id: :param args: :param kwargs: :return: """ book_obj=Book.objects.filter(pk=book_id).first() temp=Bookserializers(book_obj,context = {'request': request}) return Response(temp.data) def put(self,request,book_id,*args,**kwargs): """ 對單個對象編輯 :param request: :param book_id: :param args: :param kwargs: :return: """ book_obj=Book.objects.filter(pk=book_id).first() temp=Bookserializers(data=request.data,instance=book_obj,context = {'request': request}) if temp.is_valid(): temp.save() return Response(temp.data) else: return Response(temp.errors)
輸入:
http://127.0.0.1:8000/books/2
效果/:
{ "id": 2, "publish": "http://127.0.0.1:8000/publishes/2/", #點擊這個鏈接就可以直接看到對應出版社的信息 "title": "水滸傳1", "price": 13, "pub_date": "2018-09-05", "authors": [ { "id": 1, "name": "韓信", "age": 12 } ] }
視圖
我們已經發現publish.book.author中都需要get,put,post, delete, 這時候就會出現重復代碼的問題
這時候就用到了mixins類
使用mixins類編寫視圖
urls.py

from django.conf.urls import url from django.contrib import admin from api_demo.views import BookViewSet,BookDetailViewSet,PublishViewSet,PublishDetailViewSet urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^books/$', BookViewSet.as_view()), url(r'^publishes/$',PublishViewSet.as_view()), url(r'^books/(?P<pk>\d+)/$',BookDetailViewSet.as_view(),name="books"),#注意參數的名字必須為pk,否則會報錯 url(r'^publishes/(?P<pk>\d+)/$',PublishDetailViewSet.as_view(),name="publishes") ]
views.py

from api_demo.models import * from api_demo.serializers_base import Bookserializers,Publishserializers from rest_framework import mixins #導入mixins模塊 from rest_framework import generics #導入generics模塊 class BookViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView): queryset = Book.objects.all() serializer_class = Bookserializers def get(self, request, *args, **kwargs): return self.list(request,*args, **kwargs) def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) class BookDetailViewSet(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView): queryset = Book.objects.all() serializer_class = Bookserializers def get(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs): return self.update(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs) class PublishViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView): queryset = Publish.objects.all() serializer_class = Publishserializers def get(self, request, *args, **kwargs): return self.list(request,*args, **kwargs) def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) class PublishDetailViewSet(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView): queryset = Publish.objects.all() serializer_class = Publishserializers def get(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs): return self.update(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs)
使用的通用的基於類的視圖
通過使用mixin類,我們使用更少的代碼重寫了這些視圖,但我們還可以再進一步。REST框架提供了一組已經混合好(mixed-in)的通用視圖,我們可以使用它來簡化我們的views.py
模塊。

from api_demo.models import * from api_demo.serializers_base import Bookserializers,Publishserializers from rest_framework import mixins #導入mixins模塊 from rest_framework import generics #導入generics模塊 class BookViewSet(generics.ListCreateAPIView): # authentication_classes = [Authentication, ] queryset = Book.objects.all() serializer_class = Bookserializers class BookDetailViewSet(generics.RetrieveUpdateDestroyAPIView): queryset = Book.objects.all() serializer_class = Bookserializers class PublishViewSet(generics.ListCreateAPIView): queryset = Publish.objects.all() serializer_class = Publishserializers class PublishDetailViewSet(generics.RetrieveUpdateDestroyAPIView): queryset = Publish.objects.all() serializer_class = Publishserializers
還有進一步優化的空間 使用viewsets.ModelViewSet
urls.py
from django.conf.urls import url from django.contrib import admin from api_demo.views import BookViewSet,PublishViewSet urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^books/$', BookViewSet.as_view({"get":"list","post":"create"})), url(r'^books/(?P<pk>\d+)/$',BookViewSet.as_view({"get":"retrieve","put":"update","patch":"partial_update",'delete': 'destroy'}),name="books"), url(r'^publishes/$', PublishViewSet.as_view({"get":"list","post":"create"})), url(r'^publishes/(?P<pk>\d+)/$',PublishViewSet.as_view({"get":"retrieve","put":"update","patch":"partial_update",'delete': 'destroy'}),name="publishes") #這個參數名字必須是pk ]
views.py
from rest_framework import viewsets #導入viewsets的模塊 from django.views import View from api_demo.models import * from api_demo.serializers_base import Bookserializers,Publishserializers class BookViewSet(viewsets.ModelViewSet): queryset = Book.objects.all() serializer_class = Bookserializers class PublishViewSet(viewsets.ModelViewSet): queryset = Publish.objects.all() serializer_class = Publishserializers
關於視圖函數的總結
我們比較常用的繼承類: APIView # 適合非orm操作需要自定義功能的時候 ListAPIView # 項目實現某個功能接口,而不是增刪該的時候 ModelViewSet # 只實現增刪改查的功能
另外說一句: genericAPIView的作用:
基本沒有什么用,只是定義了接口執行流程,繼承genericAPIVIew這個類的
根據 url的不同分成兩種類型的視圖.
-------不改變url的情況下--- # 方法一:當只繼承APIView(view)時候: class BookView(APIView): def get(self): #get中最原始的方法 APIView提供你一個as_view()方法和一個dispatch()方法 你需要寫get、post等原始的方法 # 方法二當只繼承GenericAPIView(APIView):這個方法和上邊的那個方法差不多 class BookView(generics.GenericAPIView): pass APIView提供你一個as_view()方法和一個dispatch()方法 GenericAPIView提供了get_queryset()方法, 用來得到Queryset數據, 這個方法最主要的地方是要求你必須先取出queryset, 必須有否則程序報錯 assert:作用保證這個關鍵字后邊必須必須為True, 否則就會報錯. 還提供了幾個分頁的方法: paginator(), paginate_queryset(), get_paginated_response() 還提供了序列化的方法 get_serializer(), get_serializer_class() 其中這個方法你斷言你必須定義serializer_class, 沒有就報錯, 還有個方法get_serializer_context() 你需要寫 1.queryset = models.User.object.all() 2.你需要定義序列化類了 serializer_class = Bookserlizers 3.這時候你還是要寫get()方法. 1.還必須要用它的get_queryset方法來取值也就是這樣寫user_list = self.get_queryset(), \ 不要這樣寫user_list = queryset = models.User.object.all() 來替代上邊的內容, 否則會引發頁面報錯(渲染器報的錯): BrowsablepIRenderer會調用get_queryset方法然后會出現斷然錯誤 2.你需要調用self.get_serializer(user_list,many=True)得到序列化的值 # 方法三: class BookView(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView): pass mixins.CreateModelMixin提供上個方法的內容 ListModelMixin提供了list方法, 用來返回給前端序列化好的數據和提供了一個create()方法, 用來返回給前端生成並且序列化好的新數據 你需要寫get方法, 這個get方法需要return self.list() #方法四 class BookView(generics.ListCreateAPIView): pass generics.ListCreateAPIView提供了get方法和post方法,它繼承了方法三的所有方法 你只需要寫: queryset = Book.objects.all() serializer_class = Bookserlizers - -------------改變url的書寫方式在把視圖二合一- ------------------- # 第一層 class View: # 第二層 class APIView(View): #第三層 class GenericAPIView(APIView): #第四層 class GenericViewSet(ViewSetMixin, generics.GenericAPIView): # ViewSetMixin重寫as - view()方法,這個函數多接受了一個參數action,這個參數就是我們url要傳的那個字典,與APIview中的as_view不同的一點是把list方法綁定給了get屬性 #第五層 class ModelViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet): #第六層使用 class BookViewset(viewsets.ModelViewSet): 這里你只需要寫 queryset = Publish.objects.all() serializer_class = Publishserializers 這里的url需要重新寫 as_view({'get': 'list'})
認證組件
首先我們要知道什么是認證組件是干什么的,認證組件主要使用來驗證你是不是登錄了,有些頁面需要登錄后才能訪問。
django中的session也可以做認證組件,以前的博客中有介紹,但是這次我們用token來做登錄驗證。
登錄類
views.py
做認證組件首先要有一個登錄類,用於返回給瀏覽器一個token。
def get_random_str(user): """ 用md5加密獲得隨機字符串 :param user: :return: """ ctime=str(time.time()) md5=hashlib.md5(bytes(user,encoding="utf-8")) md5.update(bytes(ctime,encoding="utf-8")) return md5.hexdigest() class LoginViewset(APIView): def post(self,request,*args,**kwargs): username=request.data.get("user") password=request.data.get("pwd") user_obj=User.objects.filter(user=username,pwd=password).first() res = {"state_code": 200, "msg": None} #給前端發狀態碼, if user_obj: random_str=get_random_str(user_obj.user) user_token_obj=UserToken.objects.update_or_create(user=user_obj,defaults={"token":random_str}) # 當登錄成功后,有就更新,沒有就創建defaults這種形式由源碼決定的 res["msg"]="success" res["token"]=random_str#把驗證碼放在狀態碼中給前端, else: res["state_code"]=110 #這個是自己定義的 res["msg"]="用戶名密碼錯誤" return JsonResponse(res)
局部認證
service.auth.py
用於做認證的類,用游覽器自身攜帶的token和數據庫中的token做比較
from rest_framework import exceptions from rest_framework.authentication import BaseAuthentication#有了這個類就不需要authenticate_header方法了 from api_demo.models import * class Authentication(BaseAuthentication): def authenticate(self,request):#這個函數名字必須叫這個,源碼中規定 token=request._request.GET.get("token")) token_obj=UserToken.objects.filter(token=token).first() if not token_obj: raise exceptions.AuthenticationFailed("驗證失敗!") return (token_obj.user,token_obj)#這個形式是源碼規定的形式 (self.force_user, self.force_token)
視圖類

from django.http import JsonResponse from django.shortcuts import render,HttpResponse from django.core import serializers from rest_framework.authentication import BaseAuthentication from rest_framework.response import Response from rest_framework.views import APIView from rest_framework import serializers, viewsets, exceptions from django.views import View from api_demo.models import * from api_demo.serializers_base import Bookserializers,Publishserializers from rest_framework import mixins #導入mixins模塊 from rest_framework import generics #導入generics模塊 import hashlib,time class BookViewSet(viewsets.ModelViewSet): authentication_classes = [Authentication,] queryset = Book.objects.all() serializer_class = Bookserializers class PublishViewSet(viewsets.ModelViewSet): queryset = Publish.objects.all() serializer_class = Publishserializers
全局認證
在settings.py中這樣設置
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["api_demo.service.auth.Authentication"] #字典的鍵是源碼規定,字典的值是驗證類的路徑 }
這樣就可以全局驗證了
但是login的類不應該被驗證

class LoginViewset(APIView): authentication_classes = [ ] #加上空列表,自己有就用自己的,就不會用全局的,這樣全局就不會被驗證 def post(self,request,*args,**kwargs): username=request.data.get("user") password=request.data.get("pwd") user_obj=User.objects.filter(user=username,pwd=password).first() res = {"state_code": 200, "msg": None} #給前端發狀態碼, if user_obj: random_str=get_random_str(user_obj.user) user_token_obj=UserToken.objects.update_or_create(user=user_obj,defaults={"token":random_str}) # 當登錄成功后,有就更新,沒有就創建defaults這種形式由源碼決定的 res["msg"]="success" res["token"]=random_str#把驗證碼放在狀態碼中給前端, else: res["state_code"]=110 res["msg"]="用戶名密碼錯誤" return JsonResponse(res)
權限組件
service.permissions.py
from rest_framework.permissions import BasePermission class SvipPermissions(BasePermission): message="您沒有權限,只有Svip用戶才可以訪問" #源碼規定如果你定義message即沒有權限提示,否則就用默認的None def has_permission(self,request,abc):#這個abc參數名沒有什么要求,只是源碼中有兩個參數,這里也要有兩個參數 if request.user.user_type==3: return True else: return False
權限組件和認證組件一樣,都是用了相同的模式,全局和局部
訪問頻率組件
這個組件是用來控制單位時間內某個用戶的訪問次數的. 可以用在反爬蟲中
題目:限制用戶一分鍾訪問3次
代碼邏輯
1.先建立一個訪問記錄字典,以用戶ip作為鍵,以訪問時間點作為值,作為列表 2.先判斷當前時間,減去列表中的第一個時間, 1.如果時間大於60秒,說明不在一分鍾之內,就把第一個值給刪掉,然后把當前的時間添加到記錄字典中去. 2.如果時間小於等於60秒,說明第一個值是在一分鍾之內,然后再判斷列表中的個數是否大於3, 如果大於等於3,則返回False,則表明頻率超了,如果小於3則把當前的時間添加到記錄字典中去,.返回True
mythrottle.py
import time from rest_framework.throttling import BaseThrottle visit_record={} class MyThrottles(BaseThrottle): #繼承了這個類就不用自己定義wait了 def allow_request(self,request,abc):#源碼中規定必須有這個方法 visit_ip=request.META.get("REMOTE_ADDR") #得到用戶進來的IP地址 ,我們可也以用BaseThrottle這個類中的get_ident(request)來獲得ip地址 ctime=time.time() if visit_ip not in visit_record: visit_record[visit_ip]=[ctime] return True if len( visit_record[visit_ip])<3: #修改這里就可以修改登錄次數 visit_record[visit_ip].insert(0,ctime) return True if (ctime-visit_record[visit_ip][-1])>60: visit_record[visit_ip].insert(0, ctime) visit_record[visit_ip].pop() print(visit_record) return Trueviews.py return False
局部頻率限制
views.py
class BookViewSet(viewsets.ModelViewSet): # authentication_classes = [Authentication,] # permission_classes=[SvipPermissions] throttle_classes =[MyThrottles] queryset = Book.objects.all() serializer_class = Bookserializers
全局限制:
settings.py
REST_FRAMEWORK={"DEFAULT_THROTTLE_CLASSES":["api_demo.service.mythrottle.MyThrottles"], }
使用默認SimpleRateThrottle類
rest_framwork內置提供了幾個頻率限制類,基類就是BaseThrottle,
我們現在使用SimpleRateThrottle這個類來寫頻率限制組件
setting .py
REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES": ["api_demo.service.mythrottle.MyThrottles"], "DEFAULT_THROTTLE_RATES": { "visit_rate": "5/m", #限制登錄次數 } }
mythrottle.py
import time from rest_framework.throttling import BaseThrottle,SimpleRateThrottle class MyThrottles(SimpleRateThrottle): #SimpleRateThrottle 這個類可以幫我們任意限定登錄次數 scope = "visit_rate" #這里定義setting中的DEFAULT_THROTTLE_RATES對應的字典鍵值 def get_cache_key(self, request, view): return self.get_ident(request) #計算次數時以什么為鍵來記錄登錄時間,我們是以IP地址
解析器
Django中的request源碼
從這里進去 from django.core.handlers.wsgi import WSGIRequest

class WSGIRequest(http.HttpRequest): def __init__(self, environ): script_name = get_script_name(environ) path_info = get_path_info(environ) if not path_info: # Sometimes PATH_INFO exists, but is empty (e.g. accessing # the SCRIPT_NAME URL without a trailing slash). We really need to # operate as if they'd requested '/'. Not amazingly nice to force # the path like this, but should be harmless. path_info = '/' self.environ = environ self.path_info = path_info # be careful to only replace the first slash in the path because of # http://test/something and http://test//something being different as # stated in http://www.ietf.org/rfc/rfc2396.txt self.path = '%s/%s' % (script_name.rstrip('/'), path_info.replace('/', '', 1)) self.META = environ self.META['PATH_INFO'] = path_info self.META['SCRIPT_NAME'] = script_name self.method = environ['REQUEST_METHOD'].upper() self.content_type, self.content_params = cgi.parse_header(environ.get('CONTENT_TYPE', '')) if 'charset' in self.content_params: try: codecs.lookup(self.content_params['charset']) except LookupError: pass else: self.encoding = self.content_params['charset'] self._post_parse_error = False try: content_length = int(environ.get('CONTENT_LENGTH')) except (ValueError, TypeError): content_length = 0 self._stream = LimitedStream(self.environ['wsgi.input'], content_length) self._read_started = False self.resolver_match = None def _get_scheme(self): return self.environ.get('wsgi.url_scheme') @cached_property def GET(self): #########這是我們的GET方法 # The WSGI spec says 'QUERY_STRING' may be absent. raw_query_string = get_bytes_from_wsgi(self.environ, 'QUERY_STRING', '') return http.QueryDict(raw_query_string, encoding=self._encoding) def _get_post(self): if not hasattr(self, '_post'): self._load_post_and_files() return self._post def _set_post(self, post): self._post = post @cached_property def COOKIES(self): raw_cookie = get_str_from_wsgi(self.environ, 'HTTP_COOKIE', '') return http.parse_cookie(raw_cookie) @property def FILES(self): 1 if not hasattr(self, '_files'): self._load_post_and_files() return self._files POST = property(_get_post, _set_post)
從上邊這段代碼中,我們了解到了GET方法最后得到的是QueryDict類型的字典
我們來看一下POST源碼
def _get_post(self): #當我們request.POST方法時執行的這個方法 if not hasattr(self, '_post'): self._load_post_and_files() #我們 return self._post def _set_post(self, post): self._post = post POST = property(_get_post, _set_post)
我們來看一下這個 self._load_post_and_files()方法
if self.content_type == 'multipart/form-data':###1 當前端上傳文件時 if hasattr(self, '_body'): # Use already read data data = BytesIO(self._body) else: data = self try: self._post, self._files = self.parse_file_upload(self.META, data) except MultiPartParserError: raise elif self.content_type == 'application/x-www-form-urlencoded':##2 self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict() else:##3 self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict()
#1前端上傳是content_type == 'multipart/form-data',即文件時,我沒有看懂,但是我了解到request.Files也是走的_load_post_and_files方法
#當前端傳過來的是content-type=application/x-www-form-urlencoded, 是得到QueryDict字典。
#傳過來的其他content_type得到的是一個空的字典,
所以django中POST不能接受前端傳過來的json類型
rest_framework中的request
前端傳過來的數據,我們要用request.data中去,不能在request.POST中取了,因為rest_framework默認提供三個解析器
關於post請求
第一種情況:
請求頭為 content-type=application/x-www-form-urlencoded, 請求體為 : name='alex' print(request.data) >>><QueryDict: {'name': ['alex']}>
第二種情況:
請求頭為:Content-Type=application/json 請求體為: {'user': 'alex', 'pwd': '1234'} 后端得到的數據為: 字典類型 print (request.data) >>>>{"user": "alex", "pwd": "1234" }
之所以request.data可以得到不同的數據類型得益於默認的解析器為:
'DEFAULT_PARSER_CLASSES': ( 'rest_framework.parsers.JSONParser', 'rest_framework.parsers.FormParser', 'rest_framework.parsers.MultiPartParser' #解析表單中帶有文件的 ),
解析器根據不同的請求頭解析成不同的數據類型
關於JSONParser的源碼

class JSONParser(BaseParser): """ Parses JSON-serialized data. """ media_type = 'application/json' renderer_class = renderers.JSONRenderer strict = api_settings.STRICT_JSON def parse(self, stream, media_type=None, parser_context=None): """ Parses the incoming bytestream as JSON and returns the resulting data. """ parser_context = parser_context or {} encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) try: decoded_stream = codecs.getreader(encoding)(stream) parse_constant = json.strict_constant if self.strict else None return json.load(decoded_stream, parse_constant=parse_constant) except ValueError as exc: raise ParseError('JSON parse error - %s' % six.text_type(exc))
關於 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) #表單數據解析為QueryDict的類型 return data
路由
rest_framework也為我們的url進行了優化,我們以前寫的url還是有很大的代碼重復性,
rest_framework 中的routers模塊中DefaultRouter類中提供了方法,這個只能配合modelviewset使用,因為register只能 注冊一個視圖函數。
from django.conf.urls import url,include from django.contrib import admin from api_demo.views import BookViewSet,PublishViewSet,LoginViewset from rest_framework import routers router=routers.DefaultRouter() #生成router對象 router.register(r'books',BookViewSet) #這一句話就會生成books需要的所有的url urlpatterns = [ url(r'^',include(router.urls)), #引入include url(r'^admin/', admin.site.urls), # url(r'^books/$', BookViewSet.as_view({"get":"list","post":"create"})), # url(r'^books/(?P<pk>\d+)/$',BookViewSet.as_view({"get":"retrieve","put":"update","patch":"partial_update",'delete': 'destroy'}),name="books"), ]
這個方法的對於超鏈接Hylink來說name屬性已經變了,這時候,我們在序列化中設置hylink的時候view_name屬性應該注意。我們來看一下
^ ^publishes/$ [name='publish-list'] ^ ^publishes\.(?P<format>[a-z0-9]+)/?$ [name='publish-list'] ^ ^publishes/(?P<pk>[^/.]+)/$ [name='publish-detail'] ^ ^publishes/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='publish-detail']
渲染器
后端數據返回到前端是以什么形式展示的。該功能只是在我們的開發調試的時候用,默認有兩個類,當瀏覽器訪問時走BrowsableAPIRender類返回html頁面,當其他形式訪問時返回json形式返回。
#restframework中默認就是下面 這兩個render類,它的內部實現原理是拿url中的后綴名 .json 和類中的format字段進行比較,如果renderer_classes 中的某個類匹配成功了,那就用那個類來返回某種格式的數據 renderer_classes = [JSONRenderer,BrowsableAPIRenderer] #JSONRenderer 后端返回的數據序列化成json類型 #BrowsableAPIRenderer,將文件的content-type設置為text/html ,游覽器在獲取到這種文件時,會自動調用html解析器對文件進行相應的處理.
也就是當瀏覽器訪問的時候,restframwork默認返回給我們一個html頁面
restframework 中 render_classes 中默認就是這兩個類 源碼中規定
DEFAULTS = { # Base API policies 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', ),
注意來看一下:Browsable ApIRenderer的相關源碼
media_type = 'text/html' format = 'api' template = 'rest_framework/api.html'#當與這個類匹配的時候,會調用rest_framework中默認的HTML filter_template = 'rest_framework/filters/base.html' code_style = 'emacs' charset = 'utf-8' form_renderer_class = HTMLFormRenderer
分頁器
分頁器用來給數據分頁的
pagination.py
from rest_framework.pagination import PageNumberPagination class LufeiPagination(PageNumberPagination): page_size=2 #每頁顯示多少條數據默認顯示None max_page_size = 3 #最大每頁顯示多少個數據 page_query_param = 'page'#分頁參數按默認的顯示 例如這樣 http://127.0.0.1:8010/api/coursemoudle/?page=3 則顯示第三頁的數據 page_size_query_param = 'size'
views.py

from api.service.pagination import LufeiPagination class Courseviewset(GenericViewSet): def list(self,request,*args,**kwargs): """ 查看模塊課程 :param request: :param args: :param kwargs: :return: """ ret = {"code": 1000, "data": None, "error": None} try : #獲取數據 course_list=Course.objects.all().values('id','name').order_by('id') #對數據進行分頁 page = LufeiPagination() page_course_list = page.paginate_queryset(course_list, request, self) # 把分頁 #對數據進行序列化 temp=Courseserliazer(page_course_list,many=True) ret["data"] = temp.data except Exception as e: ret["code"] = 1001 ret['error'] = "數據獲取失敗" return Response(ret)
關於code的封裝成對象見面向對象封裝內容
學了restframework有感
1.基於反射,將類中的一些方法,封裝在配置文件中. 這是一種開放封閉原則的編程思想,這個在我們的中間件和restframework中都有體現,
優先,1 耦合度降低
2.可擴展,想擴展功能時,只要在setting中的配置文件中添加字符串路徑就可以了.
2.比如說認證,原來我對封裝的認識還不太夠,后來看了這個源碼,它里面有一種封裝的思想就是把認證這個類封裝到了request里面,以后要去用的時候,只需要調用它的方法,由它里邊具體完成這個功能。