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