rest-framework


 

前后端分離后,后端不能寫前端的代碼,那么如何發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
      ]
    }
  }
]
View Code

以上三種的缺點: 盡管能把數據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
  }
]
View Code

注意: 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
      }
    ]
  },]
View Code

對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)
View Code

輸入:

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

]
View Code

 

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)
View Code

 使用的通用的基於類的視圖

通過使用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
View Code

 

還有進一步優化的空間 使用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
View Code

全局認證

在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)
View Code

 權限組件

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

 

從上邊這段代碼中,我們了解到了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))
View Code

關於 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)
View Code

關於code的封裝成對象見面向對象封裝內容

學了restframework有感

1.基於反射,將類中的一些方法,封裝在配置文件中. 這是一種開放封閉原則的編程思想,這個在我們的中間件和restframework中都有體現,

優先,1 耦合度降低

  2.可擴展,想擴展功能時,只要在setting中的配置文件中添加字符串路徑就可以了.

開放封閉原則,其核心的思想是:
軟件實體應該是可擴展,而不可修改的。也就是說,對擴展是開放的,而對修改是封閉的.
因此,開放封閉原則主要體現在兩個方面:
對擴展開放,意味着有新的需求或變化時,可以對現有代碼進行擴展,以適應新的情況。
對修改封閉,意味着類一旦設計完成,就可以獨立完成其工作,而不要對類進行任何修改。
開放封閉原則(OCP,Open Closed Principle)是所有 面向對象原則的核心。 軟件設計本身所追求的目標就是封裝變化、降低耦合,而開放封閉原則正是對這一目標的最直接體現

2.比如說認證,原來我對封裝的認識還不太夠,后來看了這個源碼,它里面有一種封裝的思想就是把認證這個類封裝到了request里面,以后要去用的時候,只需要調用它的方法,由它里邊具體完成這個功能。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM