REST是什麼呢?它是一種架構風格,騰訊公司或其餘公司創建API時要遵照的一種規則/風格,固然也有其餘規則能夠用。html
那麼何爲REST架構風格呢?首先咱們來講說Web,由於rest是以web爲平臺的。咱們知道,web是分佈式信息系統爲超文本文件和其餘對象(資源)提供訪問入口。前端
在web上訪問一個資源,須要3點:標識,表示,交互。經過這三個操做,又引出了三個概念:uri(包括url和urn),用來識別資源;representation(例如,圖片,html,媒體)用來表示資源;經過協議與資源進行交互。因此,REST就是經過使用HTTP協議和URI,利用client/server對資源進行CRUD操做。python
那麼爲何要使用REST設計呢?確定是有它的優勢的。git
1.客戶端-服務端分離github
優勢:提升用戶界面的便攜性,經過簡化服務器提升可伸縮性....web
2..無狀態(Stateless):從客戶端的每一個請求要包含服務器所須要的全部信息django
優勢:提升可見性(能夠單獨考慮每一個請求),提升了可靠性(更容易從局部故障中修復),提升可擴展性(下降了服務器資源使用)json
有狀態與無狀態區別:
如查詢員工工資,若是查詢工資是須要登錄系統,進入查詢工資的頁面,執行相關操做,獲取工資的多少,則這種狀況下是有狀態的,由於查詢工資的每一步操做都依賴於前一步操做,只須要前面操做不成功,後序操做就沒法執行;若是輸入一個url便可獲得指定員工的工資,則這種狀況下是無狀態的,由於獲取員工工資不依賴於其餘資源或者狀態,且這種狀況下,員工工資是一個資源,由一個url與其對應,能夠經過HTTP的GET方法獲得資源。
3.緩存(Cachable):服務器返回信息必須被標記是否能夠緩存,若是緩存,客戶端可能會重用以前的信息發送請求api
優勢:減小交互次數,減小交互的平均延遲跨域
4.統一接口
優勢:提升交互的可見性,鼓勵單獨改善組件
5.支持按需代碼(Code-On-Demand 可選)
優勢:提升可擴展性
下面來解釋一下何爲表徵狀態轉移:
舉個例子:例如我訂閱了一我的的博客,想要獲取他發表的全部文章(這裏『他發表的全部文章』就是一個資源Resource)。因而我就向他的服務發出請求,說『我要獲取你發表的全部文章,最好是atom格式的』,這時候服務器向你返回了atom格式的文章列表第一頁(這裏『atom格式的文章列表』就是表徵Representation)。
你看到了第一頁的頁尾,想要看第二頁,這時候有趣的事情就來了。若是服務器記錄了應用的狀態(stateful),那麼你只要向服務詢問『我要看下一頁』,那麼服務器天然就會返回第二頁。相似的,若是你當前在第二頁,想服務器請求『我要看下一頁』,那就會獲得第三頁。可是REST的服務器偏偏是無狀態的(stateless),服務器並無保持你當前處於第幾頁,也就沒法響應『下一頁』這種具備狀態性質的請求。所以客戶端須要去維護當前應用的狀態(application state),也就是『如何獲取下一頁資源』。固然,『下一頁資源』的業務邏輯必然是由服務端來提供。服務器在文章列表的atom表徵中加入一個URI超連接(hyper link),指向下一頁文章列表對應的資源。客戶端就可使用統一接口(Uniform Interface)的方式,從這個URI中獲取到他想要的下一頁文章列表資源。上面的『可以進入下一頁』就是應用的狀態(State)。服務器把『可以進入下一頁』這個狀態以atom表徵形式傳輸(Transfer)給客戶端就是表徵狀態傳輸(REpresentational State Transfer)這個概念。
REST是面向資源進行的,而資源是經過URI進行暴露的。
URI 的設計只要負責把資源經過合理方式暴露出來就能夠了。對資源的操做與它無關,操做是經過 HTTP動詞來體現,因此REST 經過 URI 暴露資源時,會強調不要在 URI 中出現動詞。
例如:好比:左邊是錯誤的設計,而右邊是正確的
GET /rest/api/getDogs --> GET /rest/api/dogs 獲取全部小狗狗 GET /rest/api/addDogs --> POST /rest/api/dogs 添加一個小狗狗 GET /rest/api/editDogs/:dog_id --> PUT /rest/api/dogs/:dog_id 修改一個小狗狗 GET /rest/api/deleteDogs/:dog_id --> DELETE /rest/api/dogs/:dog_id 刪除一個小狗狗
REST很好利用了HTTP自己就有的一些特徵,如HTTP動詞,HTTP狀態碼,HTTP報頭等等。
更多參考:https://github.com/aisuhua/restful-api-design-references
注:上面的解釋是看見比較好的博客copy下來的,
API與用戶的通訊協議,老是使用HTTPs協議。
https://api.example.com 儘可能將API部署在專用域名(會存在跨域問題)
https://example.org/api/ API很簡單
URL,如:https://api.example.com/v1/
請求頭
跨域時,引起發送屢次請求
路徑
視網絡上任何東西都是資源,均使用名詞表示(可複數)
https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees
經過在url上傳參的形式傳遞搜索條件
更多狀態碼請看 http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
狀態碼是4xx時,應返回錯誤信息,error當作key。
{
error: 「Invalid API key」
}
針對不一樣操做,服務器向用戶返回的結果應該符合如下規範。
RESTful API最好作到Hypermedia,即返回結果中提供連接,連向其餘API方法,使得用戶不查文檔,也知道下一步應該作什麼。
{ 「link」: { 「rel」: 「collection https://www.example.com/zoos「, 「href」: 「https://api.example.com/zoos「, 「title」: 「List of zoos」, 「type」: 「application/vnd.yourformat+json」 }}
先簡單建立一個Django項目而且使用pip install djangorestframwork 安裝django rest-framwork
要想快速實現的話能夠點擊這個快速實例
在開始rest-framework說使用以前咱們先說一說序列化
開發咱們的Web API的第一件事是爲咱們的Web API提供一種將代碼片斷實例序列化和反序列化爲諸如json
之類的表示形式的方式。咱們能夠經過聲明與Django forms很是類似的序列化器(serializers)來實現。
from django.db import models # Create your models here. class Book(models.Model): title = models.CharField(max_length=32) price = models.IntegerField() pub_date = models.DateField() publish = models.ForeignKey("Publish") authors = models.ManyToManyField("Author") def __str__(self): return self.title class Publish(models.Model): name = models.CharField(max_length=32) email = models.EmailField() def __str__(self): return self.name class Author(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() def __str__(self): return self.name
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^books/', views.bookView.as_view()), ]
from django.shortcuts import render, HttpResponse from app01.models import * from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import serializers # 爲queryset,model對象作序列化===》至關於form組件使用 # 這個能夠放在一個單獨的.py文件中 class BookSerializers(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=32) price = serializers.IntegerField() pub_date = serializers.DateField() # 上面的只是針對的一對一字段的,若果用在一對多字段上的時候就會輸出關聯那張表的 def __str__(self) # publish = serializers.CharField() # 這個表示的是實現關聯表中publish表中的name字段,能夠本身定製 publish = serializers.CharField(source="publish.name") # authors=serializers.CharField(source="authors.all") # 當出現多對多的時候上面的方式也不可行,上面會顯示的是一個queryset對象 # 本身定製顯示什麼 authors = serializers.SerializerMethodField() def get_authors(self, obj): temp = [] for obj in obj.authors.all(): temp.append(obj.name) return temp # 這個至關於modelform組件同樣 class BookModelSerializers(serializers.ModelSerializer): class Meta: model = Book fields = '__all__' depth = 1 # 當遇到一對多字段的時候 publish = serializers.CharField(source='publish.name') # 多對多字段 authors = serializers.SerializerMethodField() def get_authors(self, obj): temp = [] for obj in obj.authors.all(): temp.append(obj.name) return temp # Create your views here. class BookViewSet(APIView): def get(self, request, *args, **kwargs): # 序列化 # 方式一: # book_data = list(Book.objects.all().values('name', 'email')) # return HttpResponse(book_data) # 方式二: # from django.forms.models import model_to_dict # book_data = Book.objects.all() # temp = [] # for obj in book_data: # temp.append(model_to_dict(obj)) # return HttpResponse(temp) # 方式三: # from django.core import serializers # book_data = Book.objects.all() # ret = serializers.serialize("json", book_data) # print(type(ret)) # return HttpResponse(ret) # 方式四:序列組件 # 這裏面和Django裏面的form組件和modelform組件類似 # 這裏記住要是使用瀏覽器訪問的話這個必需要在setting中的INSTALLED_APPS註冊rest_framework要不就會報錯 # 最好在項目一開始的時候就在setting裏面註冊 book_data = Book.objects.all() # many=True 表示的queryset對象,反之many=False就表示爲model對象 # 至關於form組件 # bs = BookSerializers(book_data, many=True) bs = BookModelSerializers(book_data, many=True) return Response(bs.data)
記住要把在setting裏面註冊restframework。
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', 'rest_framework' ]
在app01/utils/serializers.py 寫serializers代碼(相似於form)
from rest_framework import serializers # 爲queryset,model對象作序列化===》至關於form組件使用 class BookSerializers(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=32) price = serializers.IntegerField() pub_date = serializers.DateField() # 上面的只是針對的一對一字段的,若果用在一對多字段上的時候就會輸出關聯那張表的 def __str__(self) # publish = serializers.CharField() # 這個表示的是實現關聯表中publish表中的name字段,能夠本身定製 publish = serializers.CharField(source="publish.name") # authors=serializers.CharField(source="authors.all") # 當出現多對多的時候上面的方式也不可行,上面會顯示的是一個queryset對象 # 本身定製顯示什麼 authors = serializers.SerializerMethodField() def get_authors(self, obj): temp = [] for obj in obj.authors.all(): temp.append(obj.name) return temp
在app01/utils/serializers.py 寫ModelSerializer代碼(相似於modelform)
from app01.models import * # 導入表 from rest_framework import serializers # 這個至關於modelform組件同樣 class BookModelSerializers(serializers.ModelSerializer): class Meta: model = Book fields = '__all__' # 當遇到一對多字段的時候 publish = serializers.CharField(source='publish.name') # 多對多字段 authors = serializers.SerializerMethodField() def get_authors(self, obj): temp = [] for obj in obj.authors.all(): temp.append(obj.name) return temp
當一個model有外鍵的時候,默認顯示的是外鍵的id,此時要顯示外鍵的全部值能夠用下面,depth,會把外鍵的全部值顯示出來,depth應該是整數,代表嵌套的層級數量。
class BookModelSerializers(serializers.ModelSerializer): class Meta: model = Book fields = '__all__' # 上面表示顯示全部字段也能夠只顯示咱們想要的 # fields = ('id',) ,表示只顯示id # 要是不想顯示哪一個字段就可使用 # exclude=('id',), 表示出了id其餘的都顯示 depth = 1
上面就是關於get請求獲取全部的數據。
一樣 ModelSerializer也支持全局鉤子和局部鉤子,和form組件同樣局部鉤子爲validate_字段名(form組件爲clean_字段名),
全局鉤子爲validate。
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^books/', views.BookViewSet.as_view(), name="book"), ]
在app01/utils/serializers.py 裏面的代碼
from rest_framework import serializers
from app01.models import *
class BookModelSerializers(serializers.ModelSerializer): class Meta: model = Book fields = '__all__' publish = serializers.HyperlinkedIdentityField( view_name="detail_publish", lookup_field="publish_id", lookup_url_kwarg="pk", ) # 當遇到一對多字段的時候 # source='publish.name'這個後面是publish.name因此咱們前面傳回來的應該是publish.name對應的名字 # # 若是是publish.pk 前面傳過來的就是 publish.pk對應的id publish = serializers.CharField(source='publish.name') # 多對多字段 authors = serializers.SerializerMethodField() def get_authors(self, obj): temp = [] for obj in obj.authors.all(): temp.append(obj.name) return temp # 由於上面本身定製了publish原來的save不支持這樣保存,因此下面要重寫create方法 def create(self, validated_data): print(validated_data) # 若是上面定製的publish.name 改成 publish.pk下面這2行就不用寫了 if not validated_data["publish"]["name"].isdigit(): publish_id = Book.objects.filter(publish__name=validated_data["publish"]["name"]).values('publish_id').first() validated_data['publish_id'] = publish_id['publish_id'] else: validated_data['publish_id']=validated_data["publish"]["name"] # 其實前端通常都會傳數字不會傳漢字, 因此能夠向下面這樣寫 # validated_data['publish_id'] = validated_data["publish"]["name"] validated_data.pop('publish') authors = validated_data.pop('authors') book = Book.objects.create(**validated_data) book.authors.add(*authors) return book # def update(self, instance, validated_data): print('validated_data', validated_data) if not validated_data["publish"]["name"].isdigit(): publish_id = Book.objects.filter(publish__name=validated_data["publish"]["name"]).values('publish_id').first() validated_data['publish_id'] = publish_id['publish_id'] else: validated_data['publish_id']=validated_data["publish"]["name"] validated_data.pop('publish') authors = validated_data.pop('authors') instance.update(**validated_data) # 更新普通字段和一對多字段 nid = instance.values('id')[0]['id'] instance.first().authors.set(authors) # 更新多對多字段 book_data = Book.objects.filter(id=nid).first() # 使用下面的放法前面傳進來的instance是obj對象,而這裏傳進來的queryset對象像下面出入同樣 # book_obj = Book.objects.filter(pk=pk).first() # bs = BookModelSerializers(book_obj, data=request.data) # instance.authors.set(authors) # book_data = super().update(instance, validated_data return book_data
from django.shortcuts import render, HttpResponse from app01.models import * from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import serializers # 把Serializers和ModelSerializers單獨放在一個py文件中 from app01.utils.serilizer import * import json # Create your views here. class BookViewSet(APIView): def get(self, request, *args, **kwargs): book_data = Book.objects.all() # many=True 表示的queryset對象,反之many=False就表示爲model對象 # 至關於form組件 # bs = BookSerializers(book_data, many=True) # 這個就是至關於modelform組件, bs = BookModelSerializers(book_data, many=True) return Response(bs.data) def post(self, request, *args, **kwargs): bs = BookModelSerializers(data=request.data) if bs.is_valid(): # 直接把這個傳到後面就不須要作驗證了,對於多對多關係提交自定製顯示 bs.save(authors=request.data['authors']) return Response(bs.data) else: return Response(bs.errors) class BookDetailViewSet(APIView): def get(self, request, pk): book_obj = Book.objects.filter(pk=pk).first() bs = BookModelSerializers(book_obj, context={'request': request}) return Response(bs.data) def put(self, request, pk): book_obj = Book.objects.filter(pk=pk) # 對應serilizer裏面的 # book_obj = Book.objects.filter(pk=pk).first() # 這裏若是是一對多或者多對多的自定製的話就須要本身寫update方法 bs = BookModelSerializers(book_obj, data=request.data) if bs.is_valid(): bs.save(authors=request.data['authors']) return Response(bs.data) else: return Response(bs.errors) def delete(self, request, pk): Book.objects.filter(pk=pk).delete() return Response()
class BookDetailViewSet(APIView):
def get(self, request, pk):
book_obj = Book.objects.filter(pk=pk).first() bs = BookModelSerializers(book_obj) return Response(bs.data) def put(self, request, pk): book_obj = Book.objects.filter(pk=pk) # 這裏若是是一對多或者多對多的自定製的話就須要本身寫update方法 bs = BookModelSerializers(book_obj, data=request.data) if bs.is_valid(authors=request.data['authors']):
bs.save()
return Response(bs.data)
else:
return Response(bs.errors)
def delete(self, request, pk):
Book.objects.filter(pk=pk).delete()
return Response()
class BookModelSerializers(serializers.ModelSerializer): class Meta: model = Book fields = '__all__' publish = serializers.HyperlinkedIdentityField( view_name="detail_publish", lookup_field="publish_id", lookup_url_kwarg="pk", )
上面的黃色字段就是超連接的關鍵,
view_name表示的是該路由的別名獲取到publish/(?P<pk>\d+)/
lookup_field 表示在這個序列化裏面獲取到pk的值(也就是一個字段publish_id)
lookup_url_kwarg 表示把上面獲取到的pk值放到view_name的(?P<pk>\d+)裏面
在CBV相應部分
class BookDetailViewSet(APIView): def get(self, request, pk): book_obj = Book.objects.filter(pk=pk).first() bs = BookModelSerializers(book_obj, context={'request': request}) return Response(bs.data)
主要是在後面添加黃色部位
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^books/$', views.BookViewSet.as_view(), name="book"), url(r'^books/(?P<pk>\d+)/$', views.BookDetailViewSet.as_view(), name='detail_book'), url(r'^publish/(?P<pk>\d+)/$', views.PublishDetailViewSet.as_view(), name='detail_publish'), ]
from rest_framework import mixins from rest_framework import generics
from app01.utils.serializers import * 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)
經過使用mixin類,咱們使用更少的代碼重寫了這些視圖,但咱們還能夠再進一步。REST框架提供了一組已經混合好(mixed-in)的通用視圖,咱們可使用它來簡化咱們的views.py
模塊。
from rest_framework import mixins from rest_framework import generics
from app01.utils.serializers import * class BookViewSet(generics.ListCreateAPIView): queryset = Book.objects.all() serializer_class = BookModelSerializers class BookDetailViewSet(generics.RetrieveUpdateDestroyAPIView): queryset = Book.objects.all() serializer_class = BookModelSerializers class PublishViewSet(generics.ListCreateAPIView): queryset = Publish.objects.all() serializer_class = PublshModelSerializers class PublishDetailViewSet(generics.RetrieveUpdateDestroyAPIView): queryset = Publish.objects.all() serializer_class = PublshModelSerializers
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^books/$', views.BookViewSet.as_view({"get": "list", "post": "create"}), name="book_list"), url(r'^books/(?P<pk>\d+)$', views.BookViewSet.as_view({ 'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy' }), name="book_detail"), ]
views.py
from rest_framework import viewsets from app01.models import *
from app01.utils.serializers import *
class BookViewSet(viewsets.ModelViewSet): queryset = Book.objects.all() # BookModelSerializers和前面的同樣沒什麼變化功能相似於modelform serializer_class = BookModelSerializers
沿用上面的
token:服務端動態生成的1串用來檢驗用戶身份的字符串,能夠放在header、cokies、url參數(安全性較差)、請求體(CSRF token);
token和session相似,不一樣於 session的是token比較靈活,不只僅能夠cokies裏
url.py的代碼
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/$', views.LoginView.as_view(), name="login"), url(r'^books/$', views.BookModelView.as_view({"get": "list", "post": "create"}), name="books"), url(r'^books/(?P<pk>\d+)/$', views.BookModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}),name="detail_book"), url(r'^authors/$', views.AuthorModelView.as_view({"get": "list", "post": "create"}), name="author"), url(r'^authors/(?P<pk>\d+)/$',views.AuthorModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}), name="detail_author"), url(r'^publishers/$', views.PublisherModelView.as_view({"get": "list", "post": "create"}), name="publishers"), url(r'^publishers/(?P<pk>\d+)/$',views.PublisherModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}), name="detail_publishers"), ]
app01裏面的models.py的代碼
from django.db import models # Create your models here. class UserInfo(models.Model): USER_TYPE = ( (1, '普通用戶'), (2, 'VIP'), (3, 'SVIP') ) user_type = models.IntegerField(choices=USER_TYPE, default=1) username = models.CharField(max_length=32) password = models.CharField(max_length=64) def __str__(self): return self.username # 設置 one to one 1個用戶不能在不一樣設備上登陸 # 設置 Freikey 支持1個用戶 在不一樣設備上同時登陸 class UserToken(models.Model): user = models.OneToOneField(UserInfo, on_delete=models.CASCADE) token = models.CharField(max_length=64) def __str__(self): return self.token class Book(models.Model): title = models.CharField(max_length=32) price = models.IntegerField() pub_date = models.DateField() publish = models.ForeignKey("Publish") authors = models.ManyToManyField("Author") def __str__(self): return self.title class Publish(models.Model): name = models.CharField(max_length=32) email = models.EmailField() def __str__(self): return self.name class Author(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() def __str__(self): return self.name
CBV視圖裏面的代碼
from django.shortcuts import render from rest_framework import viewsets from rest_framework.views import APIView from app01 import models from django.http import JsonResponse from app01.utils.auth import CustomAuthentication from app01.utils.serializers import BookModelSerializers,PublishModelSerializers,AuthorModelSerializers # Create your views here. 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 LoginView(APIView): """60s內任何人都只能夠登陸3次""" authentication_classes = [] permission_classes = [] def post(self, request, *args, **kwargs): ret = {'code': 1000, '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) # 存在就更新,不存在就建立 models.UserToken.objects.update_or_create(user=obj, defaults={'token': token}) ret['token'] = token except Exception as e: ret['code'] = 1002 ret['msg'] = '請求異常' return JsonResponse(ret) class StartAuthentication(): """ 局部使用認證功能的狀況下,優先繼承該類 """ authentication_classes = [CustomAuthentication] class AuthorModelView(StartAuthentication,viewsets.ModelViewSet): """無論有沒有登錄,均可以查看且60內只能看三次""" # 若是不使用繼承類來實現局部認證就可使用下面狀況 # authentication_classes = [CustomAuthentication] queryset = models.Author.objects.all() serializer_class = AuthorModelSerializers
app01/utils/auth.py 裏面的代碼
from rest_framework import authentication,exceptions from app01.models import UserToken class CustomAuthentication(authentication.BaseAuthentication): def authenticate(self, request): token = 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.token) def authenticate_header(self, request): pass
上面的局部認證只要在相應的類裏面繼承(StartAuthentication)就好了
只要在settings.py配置
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["resdemo.service.auth.CustomAuthentication"] }
上面黃色部位就是你的token認證組件的位置(也就是上面的author.py裏面的CustomAuthentication類
若是要某一個不須要認證就在其中添加
authentication_classes = [] #裏面爲空,表明不須要認證,這個就是優先執行本身的,不執行全局的,由於本身的爲空因此就是不要認證。
如上所示咱們配置了全局模式當咱們有一個不須要認證的時候就直接在這裏面加上 authentication_classes = []
使用RestAPI認證功能小結
一、建立2張表userinfo 和usertoken表
二、認證類的authenticate方法去請求頭中獲取token信息,而後去token表中查詢token是否存在;
三、查詢到token 是正經常使用戶(返回 用戶名)不然爲匿名用戶(raise異常終止認證、或者 return none進行下一個認證)
四、局部應用
方式1::哪一個CBV須要認證在類中定義authentication_classes =[CustomAuthentication ]
方式2:額外定義1個類,CBV多繼承
方式3:全局配置使用認證功能,那個CBV不使用authentication_classes =[ ]
五、全局使用 在配置文件中配置 ,注意從新建立一個模塊,把認證類放裏面;
本身寫認證方法總結:
一、建立認證類
二、authenticate()方法返回值(三種)
三、須要建立導入的類
from rest_framework import authentication,exceptions from app01.models import UserToken
添加權限
一、在app01/utils/permission.py的文件中的代碼
#!/usr/bin/evn python #-*-coding:utf-8-*- from rest_framework import permissions class SVIPPermission(permissions.BasePermission): message = "不是SVIP不給查看" # 當沒有權限的時候提示信息 def has_permission(self, request, view): if request.user.user_type != 3: return False return True class MyPermission(permissions.BasePermission): message = "普通用戶不給查看" def has_permission(self, request, view): if request.user.user_type == 1: return False return True
二、在setting裏面設置全局權限
REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.auth.TokenAuth"], "DEFAULT_PERMISSION_CLASSES": ["app01.utils.permission.SVIPPermission"], }
若是某一個視圖不想要權限的話就能夠在該視圖中加 permission_class = []
或者不執行全局的權限只執行本身想要的權限
三、views.py 添加權限
from django.shortcuts import render from rest_framework import viewsets from rest_framework.views import APIView from app01 import models from django.http import JsonResponse from app01.utils.auth import CustomAuthentication from app01.utils.serializers import BookModelSerializers,PublishModelSerializers,AuthorModelSerializers from app01.utils.permission import MyPermission # Create your views here. 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 LoginView(APIView): """60s內任何人都只能夠登陸3次""" authentication_classes = [] permission_classes = [] def post(self, request, *args, **kwargs): ret = {'code': 1000, '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) # 存在就更新,不存在就建立 models.UserToken.objects.update_or_create(user=obj, defaults={'token': token}) ret['token'] = token except Exception as e: ret['code'] = 1002 ret['msg'] = '請求異常' return JsonResponse(ret) class StartAuthentication(): """ 局部使用認證功能的狀況下,優先繼承該類 """ authentication_classes = [CustomAuthentication] class AuthorModelView(viewsets.ModelViewSet): """無論有沒有登錄,均可以查看且""" # 不執行認證和權限認證 authentication_classes = [] permission_classes = [] queryset = models.Author.objects.all() serializer_class = AuthorModelSerializers class BookModelView(viewsets.ModelViewSet): """登錄後SVIP用戶能夠查看""" # 執行全局的權限和認證,全局的權限是隻能SVIP用戶能夠訪問 queryset = models.Book.objects.all() serializer_class = BookModelSerializers class PublisherModelView(viewsets.ModelViewSet): """登錄後普通用戶不能夠看""" # 不執行全局的權限,只執行MyPermission這個權限 permission_classes = [MyPermission] queryset = models.Publish.objects.all() serializer_class = PublishModelSerializers
四、url.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/$', views.LoginView.as_view(), name="login"), url(r'^books/$', views.BookModelView.as_view({"get": "list", "post": "create"}), name="books"), url(r'^books/(?P<pk>\d+)/$', views.BookModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}),name="detail_book"), url(r'^authors/$', views.AuthorModelView.as_view({"get": "list", "post": "create"}), name="author"), url(r'^authors/(?P<pk>\d+)/$',views.AuthorModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}), name="detail_author"), url(r'^publishers/$', views.PublisherModelView.as_view({"get": "list", "post": "create"}), name="publishers"), url(r'^publishers/(?P<pk>\d+)/$',views.PublisherModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}), name="detail_publishers"), ]
五、app01/utils/auth.py的代碼上面有不須要改變的,這裏就不寫了
總結:
(1)使用
(2)返回值
(3)局部
(4)全局
REST_FRAMEWORK = { #權限 "DEFAULT_PERMISSION_CLASSES":['app01.utils.permission.SVIPPremission'], }
from rest_framework import throttling import time VISIT_RECORD = {} class VisitThrottle(throttling.BaseThrottle): """60s內只能訪問三次""" def __init__(self): self.history = None # 初始化訪問記錄 def allow_request(self, request, view): remote_addr = self.get_ident(request) ctime = time.time() # 若是當前IP不在訪問記錄裏面,就添加到記錄 if remote_addr not in VISIT_RECORD: VISIT_RECORD[remote_addr] = [ctime, ] # 鍵值對的形式保存 return True # True表示能夠訪問 # 獲取當前ip的歷史訪問記錄 history = VISIT_RECORD.get(remote_addr) self.history = history # 若是有歷史訪問記錄,而且最先一次的訪問記錄離當前時間超過60s,就刪除最先的那個訪問記錄, # 只要爲True,就一直循環刪除最先的一次訪問記錄 # 結合下面的3次能夠知道當咱們事件 while history and history[-1] < ctime - 60: # 最先的訪問一次加60s小於當前時間 history.pop() # 若是訪問記錄不超過三次,就把當前的訪問記錄插到第一個位置(pop刪除最後一個) if len(history) < 3: history.insert(0, ctime) return True else: return False def wait(self): '''還須要等多久才能訪問''' ctime = time.time() return 60 - (ctime - self.history[-1])
from django.shortcuts import render from rest_framework import viewsets from rest_framework.views import APIView from app01 import models from django.http import JsonResponse from app01.utils.auth import CustomAuthentication from app01.utils.serializers import BookModelSerializers,PublishModelSerializers,AuthorModelSerializers from app01.utils.permission import MyPermission # Create your views here. 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 LoginView(APIView): """60s內任何人都只能夠登陸3次""" authentication_classes = [] permission_classes = [] def post(self, request, *args, **kwargs): ret = {'code': 1000, '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) # 存在就更新,不存在就建立 models.UserToken.objects.update_or_create(user=obj, defaults={'token': token}) ret['token'] = token except Exception as e: ret['code'] = 1002 ret['msg'] = '請求異常' return JsonResponse(ret) class StartAuthentication(): """ 局部使用認證功能的狀況下,優先繼承該類 """ authentication_classes = [CustomAuthentication] class AuthorModelView(viewsets.ModelViewSet): """無論有沒有登錄,均可以查看且60內只能看三次""" # 不執行認證和權限認證 authentication_classes = [] permission_classes = [] queryset = models.Author.objects.all() serializer_class = AuthorModelSerializers class BookModelView(viewsets.ModelViewSet): """登錄後SVIP用戶能夠查看不限次數""" # 執行全局的權限和認證,全局的權限是隻能SVIP用戶能夠訪問 throttle_classes = [] queryset = models.Book.objects.all() serializer_class = BookModelSerializers class PublisherModelView(viewsets.ModelViewSet): """登錄後普通用戶不能夠看且60內只能看三次""" # 不執行全局的權限,只執行MyPermission這個權限 permission_classes = [MyPermission] queryset = models.Publish.objects.all() serializer_class = PublishModelSerializers
REST_FRAMEWORK = { #節流 "DEFAULT_THROTTLE_CLASSES":['app01.utils.throttle.VisitThrottle'], }
(1)throttle.py
from rest_framework import throttling class VisitThrottle2(throttling.SimpleRateThrottle): '''匿名用戶60s只能訪問三次(根據ip)''' scope = 'NBA' # 這裏面的值,本身隨便定義,settings裏面根據這個值配置Rate def get_cache_key(self, request, view): # 經過ip限制節流 return self.get_ident(request) class UserThrottle(throttling.SimpleRateThrottle): '''登陸用戶60s能夠訪問10次''' scope = 'NBAUser' # 這裏面的值,本身隨便定義,settings裏面根據這個值配置Rate def get_cache_key(self, request, view): return request.user.username
(2)settings.py
#全局 REST_FRAMEWORK = { #節流 "DEFAULT_THROTTLE_CLASSES":['app01.utils.throttle.UserThrottle'], #全局配置,登陸用戶節流限制(10/m) "DEFAULT_THROTTLE_RATES":{ 'NBA':'3/m', #沒登陸用戶3/m,NBA就是scope定義的值 'NBAUser':'10/m', #登陸用戶10/m,NBAUser就是scope定義的值 } }
(3) views.py
from django.shortcuts import render from rest_framework import viewsets from rest_framework.views import APIView from app01 import models from django.http import JsonResponse from app01.utils.auth import CustomAuthentication from app01.utils.serializers import BookModelSerializers,PublishModelSerializers,AuthorModelSerializers from app01.utils.permission import MyPermission from app01.utils.throttle import VisitThrottle2 # Create your views here. 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 LoginView(APIView): """60s內任何人都只能夠登陸3次""" authentication_classes = [] permission_classes = [] def post(self, request, *args, **kwargs): ret = {'code': 1000, '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) # 存在就更新,不存在就建立 models.UserToken.objects.update_or_create(user=obj, defaults={'token': token}) ret['token'] = token except Exception as e: ret['code'] = 1002 ret['msg'] = '請求異常' return JsonResponse(ret) class StartAuthentication(): """ 局部使用認證功能的狀況下,優先繼承該類 """ authentication_classes = [CustomAuthentication] class AuthorModelView(viewsets.ModelViewSet): """無論有沒有登錄,均可以查看且60內只能看三次使用的是自定製的節流""" # 不執行認證和權限認證 authentication_classes = [] permission_classes = [] # 由於全局設置的是登陸用戶(10/m),這裏不須要登陸,因此就是用匿名用戶ip限流(3/m) throttle_classes = [VisitThrottle2] queryset = models.Author.objects.all() serializer_class = AuthorModelSerializers class BookModelView(viewsets.ModelViewSet): """登錄後SVIP用戶能夠查看不限次數""" # 執行全局的權限和認證,全局的權限是隻能SVIP用戶能夠訪問 throttle_classes = [] queryset = models.Book.objects.all() serializer_class = BookModelSerializers class PublisherModelView(viewsets.ModelViewSet): """登錄後普通用戶不能夠看,且SVIP用戶60s內能看10次使用的是內置的節流""" # 不執行全局的權限,只執行MyPermission這個權限 permission_classes = [MyPermission] queryset = models.Publish.objects.all() serializer_class = PublishModelSerializers
(4) 裏面的認證和權限和上面的同樣
說明:
from django.db import models # Create your models here. class UserInfo(models.Model): USER_TYPE = ( (1, '普通用戶'), (2, 'VIP'), (3, 'SVIP') ) user_type = models.IntegerField(choices=USER_TYPE, default=1) username = models.CharField(max_length=32) password = models.CharField(max_length=64) def __str__(self): return self.username # 設置 one to one 1個用戶不能在不一樣設備上登陸 # 設置 Freikey 支持1個用戶 在不一樣設備上同時登陸 class UserToken(models.Model): user = models.OneToOneField(UserInfo, on_delete=models.CASCADE) token = models.CharField(max_length=64) def __str__(self): return self.token class Book(models.Model): title = models.CharField(max_length=32) price = models.IntegerField() pub_date = models.DateField() publish = models.ForeignKey("Publish") authors = models.ManyToManyField("Author") def __str__(self): return self.title class Publish(models.Model): name = models.CharField(max_length=32) email = models.EmailField() def __str__(self): return self.name class Author(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() def __str__(self): return self.name
REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.auth.CustomAuthentication"], "DEFAULT_PERMISSION_CLASSES": ["app01.utils.permission.SVIPPermission"], "DEFAULT_THROTTLE_CLASSES": ['app01.utils.throttle.UserThrottle'], # 全局配置,登陸用戶節流限制(10/m) "DEFAULT_THROTTLE_RATES": { 'NBA': '4/m', # 沒登陸用戶4/m(表示60s4次),NBA就是scope定義的值 'NBAUser': '10/m', # 登陸用戶10/m,NBAUser就是scope定義的值 } }
#!/usr/bin/evn python #-*-coding:utf-8-*- from rest_framework import permissions class SVIPPermission(permissions.BasePermission): message = "不是SVIP不給查看" # 當沒有權限的時候提示信息 def has_permission(self, request, view): if request.user.user_type != 3: return False return True class MyPermission(permissions.BasePermission): message = "普通用戶不給查看" def has_permission(self, request, view): if request.user.user_type == 1: return False return True
#!/usr/bin/evn python # -*-coding:utf-8-*- from rest_framework import authentication,exceptions from app01.models import UserToken class CustomAuthentication(authentication.BaseAuthentication): def authenticate(self, request): token = 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.token) def authenticate_header(self, request): pass
#!/usr/bin/evn python #-*-coding:utf-8-*- from app01.models import * from rest_framework import serializers class AuthorModelSerializers(serializers.ModelSerializer): class Meta: model = Author fields = "__all__" class BookModelSerializers(serializers.ModelSerializer): class Meta: model = Book fields = "__all__" class PublishModelSerializers(serializers.ModelSerializer): class Meta: model = Publish fields = "__all__"
#!/usr/bin/evn python # -*-coding:utf-8-*- from rest_framework import throttling import time VISIT_RECORD = {} class VisitThrottle(throttling.BaseThrottle): """60s內只能訪問三次""" def __init__(self): self.history = None # 初始化訪問記錄 def allow_request(self, request, view): remote_addr = self.get_ident(request) ctime = time.time() # 若是當前IP不在訪問記錄裏面,就添加到記錄 if remote_addr not in VISIT_RECORD: VISIT_RECORD[remote_addr] = [ctime, ] # 鍵值對的形式保存 return True # True表示能夠訪問 # 獲取當前ip的歷史訪問記錄 history = VISIT_RECORD.get(remote_addr) self.history = history # 若是有歷史訪問記錄,而且最先一次的訪問記錄離當前時間超過60s,就刪除最先的那個訪問記錄, # 只要爲True,就一直循環刪除最先的一次訪問記錄 # 結合下面的3次能夠知道當咱們事件 while history and history[-1] < ctime - 60: # 最先的訪問一次加60s小於當前時間 history.pop() # 若是訪問記錄不超過三次,就把當前的訪問記錄插到第一個位置(pop刪除最後一個) if len(history) < 3: history.insert(0, ctime) return True else: return False def wait(self): '''還須要等多久才能訪問''' ctime = time.time() return 60 - (ctime - self.history[-1]) class VisitThrottle2(throttling.SimpleRateThrottle): '''匿名用戶60s只能訪問4次(根據ip)''' scope = 'NBA' # 這裏面的值,本身隨便定義,settings裏面根據這個值配置Rate def get_cache_key(self, request, view): # 經過ip限制節流 return self.get_ident(request) class UserThrottle(throttling.SimpleRateThrottle): '''登陸用戶60s能夠訪問10次''' scope = 'NBAUser' # 這裏面的值,本身隨便定義,settings裏面根據這個值配置Rate def get_cache_key(self, request, view): return request.user.username
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/$', views.LoginView.as_view(), name="login"), url(r'^books/$', views.BookModelView.as_view({"get": "list", "post": "create"}), name="books"), url(r'^books/(?P<pk>\d+)/$', views.BookModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}),name="detail_book"), url(r'^authors/$', views.AuthorModelView.as_view({"get": "list", "post": "create"}), name="author"), url(r'^authors/(?P<pk>\d+)/$',views.AuthorModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}), name="detail_author"), url(r'^publishers/$', views.PublisherModelView.as_view({"get": "list", "post": "create"}), name="publishers"), url(r'^publishers/(?P<pk>\d+)/$',views.PublisherModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}), name="detail_publishers"), ]