前后端分离后,后端不能写前端的代码,那么如何发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里面,以后要去用的时候,只需要调用它的方法,由它里边具体完成这个功能。