drf--Django rest_ framework

目录: 

  RESTful规范:面向资源编程

  APIView

  序列化组件

  认证组件

  权限组件

  频率组件

  分页组件

  视图组件

  解析组件

  响应器

  url控制器

  版本控制

  

RESTful规范:面向资源编程

-与后台做交互,通常使用https
   -包含认证书,更加具备安全性
-域名:
    -https://api.baidu.com(存在跨域问题)
    -https://www.baidu.com/api/
-版本
    -https://www.baidu.com/api/v1
    -https://www.baidu.com/api/v2
-路径,视网络上任何东西都是资源,均使用名词表示(可复数)
    -https://api.example.com/v1/books
    不是:https://api.example.com/v1/delete_one_book
-method来表示增删查改
    -https://api.example.com/v1/books  get请求,获取所有书
    -https://api.example.com/v1/books  post请求,新增一本书
    -https://api.example.com/v1/book/1  delete请求,删除一本书
    -https://api.example.com/v1/book/1  delete请求,获取id为1的这本书
    -https://api.example.com/v1/book/1  put/patch请求,修改id为1的这本书

-过滤,通过在url上传参的形式传递搜索条件
    -https://api.example.com/v1/books?limit=10:只拿前10本
    -https://api.example.com/v1/books?price=10:只拿价格为10的书
-状态码
    -{status:100,}
-错误处理,应返回错误信息,error当做key
    {
    status:101,
    errors:'您没有权限操作这个事'
    }
-返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范
    GET /collection:返回资源对象的列表(数组)
    GET /collection/resource:返回单个资源对象
    POST /collection:返回新生成的资源对象
    PUT /collection/resource:返回完整的资源对象
    PATCH /collection/resource:返回完整的资源对象
    DELETE /collection/resource:返回一个空文档
-返回结果中提供链接(获取一本书)
    {
        id:1
        name:lxx
        price:12
        publish:www.xx.com/api/v1/publish/1
    }

APIView

如果要在浏览器访问需要在app中注册:rest_framework
重写dispatch方法:
        # 传入的request是原生的request对象
        # 这个request已经不是原生的request了,但是它内部有个原生的request对象
        request = self.initialize_request(request, *args, **kwargs)
        self.initial(request, *args, **kwargs)#这里面有权限,认证,频率

序列化组件

 --

Serializer:

-1 写一个类继承
    class BookSerializer(serializers.Serializer):
        id = serializers.CharField()
        title = serializers.CharField()
        price = serializers.CharField()
-2 使用(视图类中)
    book_ser=BookSerializer(queryset对象/单个对象,many=True/False)
    book_ser.data   序列化完成的字典

 ModelSerializer:

class BookSerializer(serializers.ModelSerializer):
   class Meta:
       # 指定表模型
    model = models.Book
    # 序列化所有字段
    fields = '__all__'
    # 只想序列化title和id这俩字段
    fields = ['title', 'id']
    # exclude 和fields 不要连用
    exclude = ['title']
    #将查询自动进行到下一层,不推荐使用,不能自定义显示
    depth=1

 序列化组件的保存和修改功能:

序列化类必须继承自modelserializer类,反序列化数据,验证,保存    

def post(self, request): response = {'status': 100, 'msg': '成功'} # 提交的字典 # book = request.data # 传统方法,创建对象保存 # 新方法,通过序列化组件保存,必须用继承自ModelSerializer # data注意是data book_ser = BookSerializer(data=request.data) # is_valid提交的字段校验通过 if book_ser.is_valid(): book_ser.save() response['book'] = book_ser.data else: response['msg'] = book_ser.errors # return Response(book_ser.data) return Response(response) def put(self, request, id): response = {'status': 100, 'msg': '成功'} book = models.Book.objects.filter(pk=id).first() # 修改的话,要把book对象传过来,增加和修改的区别 book_ser = BookSerializer(data=request.data, instance=book) # is_valid提交的字段校验通过 if book_ser.is_valid(): # save既可以修改,又可以更新 book_ser.save() response['book'] = book_ser.data else: response['msg'] = book_ser.errors # return Response(book_ser.data) return Response(response)

 局部和全局钩子函数:

title = serializers.CharField(max_length=6, min_length=3, error_messages={'max_length': '太长了'})

    # 也有局部钩子和全局钩子
    def validate_title(self, value):
        from rest_framework import exceptions
        print(value)
        if value.startswith('sb'):
            raise exceptions.ValidationError('不能以sb开头')
        return value

总结:

    1 变量名和source指定的值不能一样
    2 source='publish.name'还支持继续 .
    3 source 还支持方法(没用)
    4 支持写方法,如下
        #方法一定传一个参数,是当前book对象
        publish_dic=serializers.SerializerMethodField()
            def get_publish_dic(self,obj):
                # obj应该是,当前book对象
                return {'id':obj.publish.pk,'name':obj.publish.name}

source 如果是字段,会显示字段,如果是方法,会执行方法,不用加括号(authors=serializers.CharField(source='authors.all'))

如在模型中定义一个方法,直接可以在在source指定执行。

认证组件

--

认证功能:

1 写一个类,继承BaseAuthentication
2 def authenticate(self,request) ,记住传request对象
        -如果验证通过,返回None或者两个值
3 在视图类中使用:(不要加括号)
        -authentication_classes=[AuthLogin]    

认证的配置(在要认证操作前添加或禁用):

-认证功能的局部配置
-authentication_classes=[AuthLogin]
-认证功能的全局配置,在settings.py中配置 -REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.auth.AuthLogin",] } -全局使用的局部禁用: authentication_classes = [] -drf内置了一些认证类(了解): -TokenAuthentication -SessionAuthentication -BaseAuthentication(规范了接口,模拟其它语言接口的概念) -def authenticate(self, request): raise NotImplementedError(".authenticate() must be overridden.") 如果写了一个类,继承BaseAuthentication,但是没有重写authenticate,就会抛异常

代码(登录认证):

def get_token():
    return uuid.uuid4()


class Login(APIView):
    authentication_classes = []
    # def get(self,request):
    #     return Response(111)
    def post(self,request):
        response = {'status': 100, 'msg': None}
        name = request.data.get('name')
        print(name)
        pwd = request.data.get('pwd')
        # print(pwd)
        user = models.User.objects.filter(name=name,password=pwd).first()
        # print(user)
        if user:

            response['msg'] = '登录成功'
            token = get_token()
            models.User_token.objects.update_or_create(defaults={"token":token},user=user)
            response['token'] = token

        else:
            response['status'] = 101,
            response['msg'] = '登录失败'
        return Response(response)


class Get_User(APIView):
    authentication_classes = [AuthLogin]
    def get(self,request):
        token = request.GET.get('token')
        print(token)

        return Response(123)
views.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^login/', views.Login.as_view()),
    url(r'^getuser/', views.Get_User.as_view()),
urls.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import NotAuthenticated
from app01 import models
class AuthLogin(BaseAuthentication):
    def authenticate(self,request):
        token=request.GET.get('token')
        print(token)
        ret=models.User_token.objects.filter(token=token).first()
        print(ret)
        if ret:
            #说明这个人登录了
            return ret.user,token
            # return None
        else:
            # 说明没有登录
            raise NotAuthenticated('您没有登录')
    # 要写这个方法,
    def authenticate_header(self,request):
        pass
auth.py

 settings

权限组件

--

功能实现:

-写一个类:

    class MyPer(BasePermission):
        message='您没有权限'
        def has_permission(self, request, view):
            # 取出当前登录用户
            user = request.user
            # 取出当前登录用户类型的中文
            tt = user.get_user_type_display()
            if user.user_type == 0:
                return True
            else:
                return False

 使用:

-局部使用
在视图函数中
   permission_classes=[MyPer]
-全局使用
在setting中
    "DEFAULT_PERMISSION_CLASSES":['app01.auth.MyPer'],

频率组件

限制单位时间访问次数:

-限制每个ip地址一分钟访问10次
-写一个类
继承drf来实现:
from
rest_framework.throttling import SimpleRateThrottle class VisitThrottle(SimpleRateThrottle): scope = 'xx' #一定要写它 # 必须重写这个方法,返回什么,就以什么作为访问频率限制的东西, def get_cache_key(self, request, view): # return self.get_ident(request) return request.META.get('REMOTE_ADDR') -在setting中配置: 'DEFAULT_THROTTLE_RATES': { #这个key跟scope对应,value值3/m 3/h 4/d 'xx': '3/m' }

自定义实现:
 1 class MyTho():
 2     VISIT_RECORD = {}
 3     def __init__(self):
 4         self.history = None
 5     def allow_request(self,request,view):
 6         # 访问频率的逻辑
 7         #     {'ip地址':[16:13:39,16:13:19,],'ip地址2':[时间1,时间2],}
 8         # (1)取出访问者ip
 9         # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
10         # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
11         # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
12         # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
13         #(1)取出访问者ip
14         # print(request.META)
15         # REMOTE_ADDR 就是访问者的ip:127.0.0.1
16         ip=request.META.get('REMOTE_ADDR')
17         import time
18         # 获取当前时间
19         ctime=time.time()
20         # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问
21         if ip not in self.VISIT_RECORD:
22             self.VISIT_RECORD[ip]=[ctime,]
23             # {'127.0.0.1':[时间1,时间1,]}
24             return True
25         # self.history=[时间1,时间1,]
26         self.history=self.VISIT_RECORD.get(ip)
27         # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
28         while self.history and ctime-self.history[-1]>60:
29             self.history.pop()
30         # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
31         # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
32         if len(self.history)<3:
33             self.history.insert(0,ctime)
34             return True
35         else:
36             return False
37 
38     def wait(self):
39         import time
40         ctime = time.time()
41         # 还要等多久才能访问
42         return 60 - (ctime - self.history[-1])
自定义一分钟访问3次

 使用:

    -局部使用:
        throttle_classes = [VisitThrottle]
    -全局使用:
        "DEFAULT_THROTTLE_CLASSES":["app01.auth.VisitThrottle"],
    -问题?如果网站所有的接口都是一分钟访问10次,但是单独有一个接口要一分钟访问2次,如何做?
     单独为其写一个类,来进行频率控制

分页组件

--

简单分页:

page_size = api_settings.PAGE_SIZE    :每页显示条数
page_query_param = 'page'              :查询的页码数
page_size_query_param = None          :每页最大显示条数
    -如果我要的数据是第一页显示10条:http://127.0.0.1:8000/books/?ccc=10&page=1
    -如果我要的是第二页显示5条:http://127.0.0.1:8000/books/?ccc=5&page=2
max_page_size = 8                    :控制最大显示多少条
-如果我想第一页显示10000条,这种不合理,通过这个参数控制,最大显示8条
 1 from rest_framework.pagination import PageNumberPagination
 2 class Books(APIView):
 3     def get(self, request):
 4         response = {'status': 100, 'msg': '成功'}
 5         # 生成一个PageNumberPagination的对象
 6         page_sipmle=PageNumberPagination()
 7         page_sipmle.page_size=4
 8         # page_sipmle.page_query_param='aaa'
 9         page_sipmle.page_size_query_param='ccc'
10         page_sipmle.max_page_size=8
11         book_list = models.Book.objects.all()
12         page_list = page_sipmle.paginate_queryset(book_list,request,self)
13         book_ser = BookSerializer(instance=page_list, many=True)
14 
15         print(book_ser.data)
16         response['books'] = book_ser.data
17         return Response(response)
18 
19     def post(self, request):
20         response = {'status': 100, 'msg': '成功'}
21         book_ser = BookSerializer(data=request.data)
22         if book_ser.is_valid():
23             book_ser.save()
24             response['book'] = book_ser.data
25         else:
26             response['msg'] = book_ser.errors
27         return Response(response)
简单分页

  使用: 

page_sipmle=PageNumberPagination()
page_sipmle.page_size=4
# page_sipmle.page_query_param='aaa'
page_sipmle.page_size_query_param='ccc'
page_sipmle.max_page_size=8
book_list = models.Book.objects.all()
page_list = page_sipmle.paginate_queryset(book_list,request,self)
book_ser = BookSerializer(instance=page_list, many=True)

偏移分页:

default_limit = api_settings.PAGE_SIZE :默认偏移的条数 5
limit_query_param = 'limit'       :偏移的条数
offset_query_param = 'offset'     :是标杆
max_limit = None                  :最大偏移的条数
 1 from rest_framework.pagination import LimitOffsetPagination
 2 class Books(APIView):
 3     def get(self, request):
 4         response = {'status': 100, 'msg': '成功'}
 5         # 生成一个PageNumberPagination的对象
 6         page_limit=LimitOffsetPagination()
 7         page_limit.default_limit=6
 8         page_limit.max_limit=5
 9         book_list = models.Book.objects.all()
10         page_list = page_limit.paginate_queryset(book_list,request,self)
11         book_ser = BookSerializer(instance=page_list, many=True)
12         print(book_ser.data)
13         response['books'] = book_ser.data
14         return Response(response)
偏移分页

  举例:

default_limit = api_settings.PAGE_SIZE :默认偏移的条数 5
limit_query_param = 'limit'       :偏移的条数
offset_query_param = 'offset'     :是标杆
max_limit = None                  :最大偏移的条数

加密分页(游标分页):

cursor_query_param = 'cursor'
page_size = api_settings.PAGE_SIZE    :默认每页显示的条数
ordering = 'id'                       :按谁排序
4  5  6        10
 1 from rest_framework.pagination import CursorPagination
 2 class Books(APIView):
 3     def get(self, request):
 4         response = {'status': 100, 'msg': '成功'}
 5         # 生成一个PageNumberPagination的对象
 6         page_cursor=CursorPagination()
 7         page_cursor.page_size=5
 8         page_cursor.ordering="id"
 9         book_list = models.Book.objects.all()
10         page_list = page_cursor.paginate_queryset(book_list,request,self)
11         book_ser = BookSerializer(instance=page_list, many=True)
12         print(book_ser.data)
13         response['books'] = book_ser.data
14         # return Response(response)
15         return page_cursor.get_paginated_response(book_ser.data)
加密分页

视图组件

--

视图封装

--

视图组件使用

ViewSetMixin

ViewSetMixin 写在前面,先找ViewSetMixin的as_view方法
用了ViewSetMixin ,视图类中,不需要再写get,post,delete....这些函数了,函数名可以自定义
而且这个视图类,可以响应多条路由

  使用:

-urls.py中
url(r'^publishs/', views.PublishView.as_view({'get': 'aaa','post':'ddd'})),
url(r'^bbb/', views.PublishView.as_view({'get': 'bbb','post':'ccc'})),

-视图类中:
class PublishView(ViewSetMixin,APIView):
  def aaa(self,request):
    return Response({'status':100})
  def bbb(self,request):
    return Response({'bb': "bbb"})

解析组件

根据请求头 content-type 选择对应的解析器对请求体内容进行处理。

能够解析前端传过来的什么格式数据(urlencoded,form-data,json),一般只用json来保障安全性

有application/json,x-www-form-urlencoded,form-data等格式

-源码从request.data进
-全局配置
    -'DEFAULT_PARSER_CLASSES':['rest_framework.parsers.JSONParser'],
-局部配置
    parser_classes=[JSONParser,]

响应器(一般用默认,不用写)

根据 用户请求URL 或 用户可接受的类型,筛选出合适的 渲染组件。
可以根据请求客户端类型,返回不同格式数据。
用户请求URL:
    http://127.0.0.1:8000/test/?format=json
    http://127.0.0.1:8000/test.json

-局部配置 renderer_classes=[JSONRenderer,] -全局配置 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', )

url控制器

-传统的url配置
    url(r'^books/$', views.BookView.as_view()),
    url(r'^books/(?P<pk>\d+)$', views.BookDetailView.as_view()),
-半自动
  --view要ViewSetMixin,重写as_view函数 url(r
'^publish/$', views.PublishView.as_view({'get':'list','post':'create'})), url(r'^publish/(?P<pk>\d+)/$', views.PublishView.as_view({'get':'retrieve','put':'update','delete':'destroy'})),
-全自动(了解) -能自动生成多条路由

版本控制 

-在setting中配置
    'DEFAULT_VERSION': 'v1',  # 默认版本(从request对象里取不到,显示的默认值)
    'ALLOWED_VERSIONS': ['v1', 'v2'],  # 允许的版本
    'VERSION_PARAM': 'version'  # URL中获取值的key

-局部使用
    versioning_class = URLPathVersioning
-全局使用
   'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning',

url.py文件,可以用路由分发来使用不同版本
url(r'^(?P<version>[v1|v2]+)/publishs/', views.PublishView.as_view()),
url(r'^(?P<version>[v1|v2]+)/publishs/', views.publish)

 

posted @ 2019-02-19 20:30  ChuckXue  阅读(295)  评论(0)    收藏  举报