web开发模式
前后端混合开发(前后端不分离);返回的是html的内容,需要写模板
前后端分离: 只专注于写后端接口,返回json,xml格式数据
什么是动态页面(查数据库的),什么是静态页面(静止的html)
页面静态化
api接口
通过网络,规定了前后台信息交互规则的url连接,也就是前后台信息交互的媒介
postman的使用
点击安装后自动打开
解析json的网站
postman是目前最好用的,模拟发送http请求的工具
请求头中User-Agent: 客户端的类型
请求头中加其他参数
Restful规范
REST全称是Representational State Transfer,中文意思是表述表征性状态转移
Restful是一种定义Web API接口的设计风格,尤其适用于前后端分离的应用模式中
这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源
事实上,我们可以使用任何一个框架都可以实现符合restful规范的API接口
抓包工具: fiddler, chales
10条规范
1. 数据的安全保障: url链接一般都采用https协议进行传输 注:采用https协议,可以提高数据交互过程中的安全性
2. 接口特征表现
- 用api关键字标识urlL
- https://api.baidu.com
注: 看到api字眼,就代表该请求url链接是完成前后端数据交互的
3. 多数据版本共存
在url链接中标识数据版本
注:url链接中的v1、v2就是不同数据版本的体现(只有在一种数据资源有多版本情况下)
4. 数据即是资源,均使用名词(可复数)
接口一般都是完成前后台数据的交互,交互的数据我们称之为资源
https://api.baidu.com/users
注:一般提倡用资源的复数形式,在url链接中奖励不要出现操作资源的动词,错误示范:https://api.baidu.com/delete-user
特殊的接口可以出现动词,因为这些接口一般没有一个明确的资源,或是动词就是接口的核心含义
5. 资源操作由请求方式决定(method)
操作资源一般都会涉及到增删改查,我们提供请求方式来标识增删改查动作
- https://api.baidu.com/books - get请求:获取所有书
- https://api.baidu.com/books/1 - get请求:获取主键为1的书
- https://api.baidu.com/books - post请求:新增一本书书
- https://api.baidu.com/books/1 - put请求:整体修改主键为1的书
- https://api.baidu.com/books/1 - patch请求:局部修改主键为1的书
- https://api.baidu.com/books/1 - delete请求:删除主键为1的书
6. 过滤,通过url传参的形式传递搜索条件
7. 响应状态码
正常响应
- 响应状态码2xx
- 200:常规请求
- 201:创建成功
重定向响应
- 响应状态码3xx
- 301:永久重定向
- 302:暂时重定向
客户端异常
- 响应状态码4xx
- 403:请求无权限
- 404:请求路径不存在
- 405:请求方法不存在
服务器异常
- 响应状态码5xx
- 500:服务器异常
8. 错误处理,应返回错误信息,error当做key
9. 返回结果,针对不同操作,服务端向用户返回的结果应该符合以下规范
10 需要url请求的资源需要访问资源的请求链接
drf的安装和简单使用
安装: pip install djangorestframework
使用:
1. 在settings.py的app中注册
INSTALLED_APPS = [ 'rest_framework', ]
2. 在models.py中写表模型
class Book(models.Model): nid = models.AutoField(primary_key = True) name = models.CharField(max_length=32) price = models.DecimalField(max_digits=5,decimal_places=2) author = models.CharField(max_length=32)
3. 新建一个序列化类(在app01下新建一个py文件,如ser.py)
from rest_framework.serializers import ModelSerializer from app01.models import Book class BookModelSerializer(ModelSerializer): class Meta: model = Book fields = '__all__'
4. 在视图中写视图类
from rest_framework.viewsets import ModelViewSet from .models import Book from .ser import BookModelSerializer # Create your views here. class BooksViewSet(ModelViewSet): queryset = Book.objects.all() serializer_class = BookModelSerializer
5. 写路由
from django.contrib import admin from django.urls import path from app01 import views from rest_framework.routers import DefaultRouter router = DefaultRouter() router.register('book',views.BooksViewSet) urlpatterns = [ path('admin/', admin.site.urls), ] # 两个列表相加 urlpatterns += router.urls
CBV源码
视图类,必须继承View(读view的源码)
在类里写get,post方法就可以了,只要get请求来,就会走get方法(方法跟fbv写法完全一样)
路由:类名.as_view() 如: views.Books.as_view() - 这个函数执行完一定是一个内存地址 -> view内存函数的地址
请求来了,路由匹配上 -> view(request) -> self.dispatch(request,*args,**kwargs)
dispatch -> 把请求方法转成小写 -> 通过反射,去对象中找,有没有get方法,有就加括号执行,并且把request传进去了
ModelviewSet 继承View(django原生view)
APIView继承了View
先读view的源码
from django.views import View
urls.py
path('books1',views.Books.as_view()), # 在这个地方应该写个函数内存地址, views.Books.as_view() 执行完了之后是个函数内存地址,as_view是一个类方法,类直接来调用,会把类自动传入
放了一个view的内存地址(View-> as_view->内层函数)
请求来 了,如果路径匹配,会执行,函数内存地址(request)
def view(request, *args, **kwargs): #request是当次请求的request对象 self = cls(**initkwargs) #实例化得到一个对象,Book对象 self.setup(request, *args, **kwargs) if not hasattr(self, 'request'): raise AttributeError( "%s instance has no 'request' attribute. Did you override " "setup() and forget to call super()?" % cls.__name__ ) return self.dispatch(request, *args, **kwargs) def dispatch(self, request, *args, **kwargs): # request就是当次请求的request,self是book对象 # Try to dispatch to the right method; if a method doesn't exist, # defer to the error handler. Also defer to the error handler if the # request method isn't on the approved list. if request.method.lower() in self.http_method_names: #handler现在是 handler = getattr(self,'get') 你写的Book类的get方法的内存地址 handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs) # 执行get(request)
API View源码分析
drf提供的,扩展了view的功能
视图类,继承APIView(读APIView的源码)
在类里写get,post方法就可以了,只要get请求来,就会走get方法(方法跟fbv写法完全一样)
路由:类名.as_view() 如: views.Books.as_view() - 这个函数执行完一定是一个内存地址 -> view内存函数的地址,处理了csrf,所有请求,都没有csrf校验了
请求来了,路由匹配上 -> view(request) -> self.dispatch(request,*args,**kwargs),现在这个dispatch不是view中的dispatch,而是APIView中的dispatch
from rest_framework.views import APIView
urls.py中
path('booksapiview/',views.BooksAPIView.as_view()) # 在这个地方是内存地址
APIView的as_view方法(类的绑定方法)
@classmethod def as_view(cls, **initkwargs): view = super().as_view(**initkwargs) # 调用父类(view)的as_view()方法 view.cls = cls view.initkwargs = initkwargs # Note: session based authentication is explicitly CSRF validated, # all other authentication is CSRF exempt. # 以后所有的请求,都没有csrf认证了。只要继承了APIView,就没有csrf的认证了 return csrf_exempt(view)
请求来了 -> 路由匹配上 -> view(request) -> 调用了 self.dispatch(),会执行APIView的dispatch
APIView的dispatch方法
def dispatch(self, request, *args, **kwargs): """ `.dispatch()` is pretty much the same as Django's regular dispatch, but with extra hooks for startup, finalize, and exception handling. """ self.args = args self.kwargs = kwargs request = self.initialize_request(request, *args, **kwargs) self.request = request self.headers = self.default_response_headers # deprecate? try: self.initial(request, *args, **kwargs) # Get the appropriate handler method if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed response = handler(request, *args, **kwargs) except Exception as exc: response = self.handle_exception(exc) self.response = self.finalize_response(request, response, *args, **kwargs) return self.response
drf中的Request类
from rest_framework.request import Request
只要继承了APIView,视图类中的request对象,都是新的,也就是上面那个request的对象了
老的request在新的request._request里
以后使用request对象,就像使用之前的request是一模一样的(因为重写了__getattr__方法)
def __getattr__(self, attr): """ If an attribute does not exist on this instance, then we also attempt to proxy it to the underlying HttpRequest object. """ try: return getattr(self._request, attr) # 通过反射,取原生的request对象,取出属性或方法 except AttributeError: return self.__getattribute__(attr)
request.data 感觉是个数据属性,其实是个方法,@property,修饰了
它是一个字典,post请求不管使用什么编码,传过来的数据,都在request.data里
get请求中传过来数据,从哪取?
@property def query_params(self): """ More semantically correct name for request.GET. """ return self._request.GET
self.perform_authentication(request) self.check_permissions(request) self.check_throttles(request)
序列化组件
1. 序列化,序列化器会把模型对象转换成字典,经过response以后变成json字符串
2. 反序列化,把客户端发送过来的数据,经过request以后转换成字典,序列化器可以把字典转换成模型
3. 反序列化,完成数据校验功能
序列化组件简单使用
1. 写一个序列化的类,继承serializer
2. 在类中写要序列化的字段,想序列化哪个字段,就在类中写哪个字段
3. 在视图类中使用,导入 -> 实例化得到序列化类的对象,把要序列化的对象传入
4. 序列化类的对象.data 是一个字典
5. 把字典返回,如果不使用rest_framework提供的Response,就得使用JsonResponse
序列化类的字段类型
有很多,不需要都记住
CharField, IntegerField,DateField...
序列化字段选项
序列化组件修改数据
1. 写一个序列化的类,继承Serializer
2. 在类中写要反序列化的字段,想反序列化哪个字段,就在类中写哪个字段,字段的属性(max_length,min_length...)
3. 在视图类中使用,导入-> 实例化得到序列化类的对象,把要修改的对象传入,要修改的数据传入
book_ser = BookSerializer(book, request.data) book_ser = BookSerializer(instance=book, data=request.data)
4. 数据校验
5. 如果数据校验通过,就保存
if book_ser.is_valid(): # 返回True表示验证通过 book_ser.save() # 报错 # 注意不是book.save() , 而是book序列化器的save response_msg['data'] = book_ser.data
6. 如果不通过,逻辑自己写
7. 如果字段的校验规则不够,可以写钩子函数(局部和全局)
8. 可以使用字段的validators来校验
写一个函数
def check_author(data): if data.startswith('sb'): raise ValidationError('作者名字不能以sb开头') else: return data
配置
author = serializers.CharField(validators=[check_author])
read_only 和 write_only
read_only 表明该字段仅用于序列化输出,默认False,如果设置成True,postman中可以看到该字段,修改时,不需要传该字段
write_only 表名该字段仅用于反序列化输入,默认False,如果设置成True,postman中看不到该字段,修改时,该字段必须传
查询所有
views.py
class BooksView(APIView): def get(self,request): response_msg = {'status':100,'msg':'成功'} books = Book.objects.all() book_ser = BookSerializer(books,many=True) # 序列化多条,如果序列化一条,不需要写 response_msg['data'] = book_ser.data return Response(response_msg)
urls.py
path('books/',views.BooksView.as_view()),
新增数据
views.py
class BooksView(APIView): def post(self,request): # 修改的时候才有instance, 新增没有instance, 只有data response_msg = {'status':100,'msg':'成功'} book_ser = BookSerializer(data=request.data) # book_ser = BookSerializer(request.data) # 这个按位置传request.data给instance,就报错了 # 校验字段 if book_ser.is_valid(): book_ser.save() response_msg['data'] = book_ser.data else: response_msg['status'] = 102 response_msg['msg'] = '数据校验失败' response_msg['data'] = book_ser.errors return Response(response_msg)
ser.py 序列化类重写create方法
def create(self, validated_data): instance = Book.objects.create(**validated_data) return instance
urls.py
path('books/',views.BooksView.as_view()),
删除一个数据
class BookView(APIView): def delete(self,request,pk): ret = Book.objects.filter(id=pk).delete() response_msg = {'status':100,'msg':'删除成功'} return Response(response_msg)
urls.py
re_path('books/(?P<pk>\d+)',views.BookView.as_view()),
自己封装Response对象
class MyResponse(): def __init__(self): self.status = 100 self.msg= '成功' @property def get_dict(self): return self.__dict__ if __name__ == '__main__': res = MyResponse() res.status = 101 res.msg = '查询失败' res.data = {'name':'jeffrey'} print(res.get_dict)
模型序列化器
class BookModelSerializer(serializers.ModelSerializer): class Meta: model = Book # 对应上models.py中的模型 # fields = '__all__' # fields = ('name','price') # 只序列化指定的,字段 exclude = ('name',) # 跟fields不能都写,写谁,就表示排除谁 # read_only_fields = ('price',) # 弃用了 # write_only_fields = ('author',) #弃用了 使用extra_kwargs extra_kwargs = { 'price':{'write_only':True}, 'author':{'write_only':True} }
其他使用一模一样
不需要重写create和update方法了
源码分析
序列化多条,需要传many= True
book = Book.objects.all().first() book_ser = BookModelSerializer(books,many=True) print(type(book_ser)) # <class 'rest_framework.serializers.ListSerializer'> book_one_ser = BookSerializer(book) print(type(book_one_ser)) #<class 'app01.ser.BookSerializer'>
对象的生成 - > 先调用类的 __new__方法,生成空对象
对象 = 类名(name='jeffrey') 触发类的__init__ 方法
类的__new__方法控制对象的生成
def __new__(cls, *args, **kwargs): if kwargs.pop('many', False): return cls.many_init(*args, **kwargs) # 没有传 many = True的时候,走下面正常的对象实例化 return super().__new__(cls, *args, **kwargs)
Serializer高级用法
source的使用
1. 可以改字段名字
xxx = serializers.CharField(source='title')
2. 可以.跨表
publish = serializers.CharField(source='publish.email')
3. 可以执行方法(test是Book表模型中的方法)
pub_date = serializers.CharField(source='test')
SerializerMethodField()的使用
1. 它需要有个配套方法,方法名叫get_字段名, 返回值就是要显示的东西
authors = serializers.SerializerMethodField() # 它需要有一个配套方法,方法名叫get_字段名,返回值就是要显示的东西 def get_authors(self,instance): # print('instance:',instance,type(instance)) # instance就是book对象 authors = instance.authors.all() # 取出所有作者 print(authors) ll = [] for author in authors: ll.append({'name':author.name,'age':author.age}) print(ll) return ll
在实际开发中碰到的问题及如何解决的
read_only_fields = ('price',) write_only_fields = ('author',)
以上不能用了,使用 extra_kwargs解决了
extra_kwargs = { 'price':{'write_only':True} }
请求和响应
请求对象
def __init__(self, request, parsers=None, authenticators=None, negotiator=None, parser_context=None): assert isinstance(request, HttpRequest), ( 'The `request` argument must be an instance of ' '`django.http.HttpRequest`, not `{}.{}`.' .format(request.__class__.__module__, request.__class__.__name__) ) # 二次封装request,把原生request作为drf request对象的 _request属性 self._request = request def __getattr__(self,attr): return getattr(self_request,attr)
请求对象.data: 前端以三种编码方式传入的数据,都可以取出来
请求对象.query_params与django标准的request.GET相同,只是更换了更正确的名称而已
响应对象
from rest_framework.response import Response def __init__(self, data=None, status=None, template_name=None, headers=None, exception=False, content_type=None):
data: 你要返回的数据,字典
status:返回的状态码,默认是200
from rest_framework import status # status在这个路径下,把所有状态码都定义成了常量
template_name: 渲染的模板的名字(自定制模板),不需要了解
headers: 响应头,可以往响应头里放东西,就是一个字典
content_type: 响应的编码格式
浏览器响应成浏览器的格式,postman响应成json格式,通过配置实现的(默认配置)
# 不管是postman还是浏览器都返回json格式数据
drf有默认的配置文件 -> 先从项目的settigns中找,找不到,采用默认的
drf的配置信息,先从自己类中找,再去项目的settings.py中找,再去默认的找
- 局部使用:对某个视图类有效
from rest_framework.renderers import JSONRenderer # Create your views here. class TestView(APIView): renderer_classes = [JSONRenderer, ] def get(self,request): print('request:',request) return Response({'name':'jeffrey'},status=status.HTTP_200_OK,headers={'token':'xxx'})
- 全局使用: 全局的视图类,所有请求,都有效
- 在settings.py中加入如下
视图
两个视图基类
APIView
GenericAPIView
基于APIView写接口
views.py
class BookView(APIView): def get(self,request): book_list = Book.objects.all() book_ser = BookSerializer(book_list,many=True) return Response(book_ser.data) def post(self,request): book_ser = BookSerializer(data=request.data) if book_ser.is_valid(): book_ser.save() return Response(book_ser.data) else: return Response({'status':101,'msg':'校验失败'}) class BookDetailView(APIView): def get(self,request,pk): book = Book.objects.filter(id=pk).first() book_ser = BookSerializer(book) return Response(book_ser.data) def put(self,request,pk): book = Book.objects.filter(id=pk).first() book_ser = BookSerializer(instance=book,data=request.data) if book_ser.is_valid(): book_ser.save() return Response(book_ser.data) else: return Response({'status':101,'msg':'校验失败'}) def delete(self,request,pk): Book.objects.filter(id=pk).first().delete() return Response({'status':100,'msg':'删除成功'})
models.py
class Book(models.Model): name = models.CharField(max_length=32) price = models.DecimalField(max_digits=5,decimal_places=2) publish = models.CharField(max_length=32) class Publish(models.Model): name = models.CharField(max_length=32) email = models.CharField(max_length=32)
ser.py
from rest_framework import serializers from app01.models import Book,Publish class BookSerializer(serializers.ModelSerializer): class Meta: model = Book fields = '__all__' class BookSerializer2(serializers.ModelSerializer): class Meta: model = Book fields = ['name'] class PublishSerializer(serializers.ModelSerializer): class Meta: model = Publish fields = '__all__' class PublishSerializer2(serializers.ModelSerializer): class Meta: model = Publish fields = ['name',]
urls.py
path('books/',views.BookView.as_view()), re_path('books/(?P<pk>\d+)',views.BookDetailView.as_view()),
基于GenericAPIView写的接口
views.py
class BooksGenericView(GenericAPIView): print('ccc') # queryset要传queryset对象,查询了所有的图书 # serializer_class : 使用哪个序列化类来序列化这堆数据 queryset = Book.objects.all() serializer_class = BookSerializer2 # 当视图中使用多个序列化器类时,可以使用该方法来区分 # def get_serializer_class(self): # if self.request.method == 'GET': # return BookSerializer2 # else: # return BookSerializer def get(self,request): print('get:',self.get_serializer()) book_list = self.get_queryset() book_ser = self.get_serializer(book_list,many=True) return Response(book_ser.data) def post(self,request): print('post:',self.get_serializer()) book_ser = self.get_serializer(data=request.data) if book_ser.is_valid(): book_ser.save() return Response(book_ser.data) else: return Response({'status':101,'msg':'校验失败'}) class BookDetailGenericView(GenericAPIView): queryset = Book.objects.all() serializer_class = BookSerializer def get(self,request,pk): book = self.get_object() book_ser = self.get_serializer(book) return Response(book_ser.data) def put(self,request,pk): book = self.get_object() book_ser = self.get_serializer(instance=book,data=request.data) if book_ser.is_valid(): book_ser.save() return Response(book_ser.data) else: return Response({'status':101,'msg':'校验失败'})
基于GenericAPIView和5个视图扩展类写的接口
views.py
from rest_framework.mixins import ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin # 基于视图扩展类来写视图接口 class Books3View(GenericAPIView,ListModelMixin,CreateModelMixin): queryset = Book.objects.all() serializer_class = BookSerializer def get(self,request): return self.list(request) def post(self,request): return self.create(request) class Book3DetailView(GenericAPIView,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin): queryset = Book.objects.all() serializer_class = BookSerializer def get(self,request,pk): return self.retrieve(request,pk) def put(self,request,pk): return self.update(request,pk) def delete(self,request,pk): return self.destroy(request,pk)
使用ModelViewSet写5个接口
views.py
from rest_framework.viewsets import ModelViewSet class Book5View(ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializer
urls.py
path('books5',views.Book5View.as_view(actions={'get':'list','post':'create'})),#当路径匹配,又个get请求,会执行Book5View的get方法 re_path('books5/(?P<pk>\d+)',views.Book5View.as_view(actions={'get':'retrieve','put':'update','delete':'destroy'})),
源码分析ViewSetMixin
for method, action in actions.items(): handler = getattr(self, action) setattr(self, method, handler)
继承ViewSetMixin的视图类
views.py
class Book6View(ViewSetMixin,APIView): # 一定要放在APIView前面 def get_all_book(self,request): print('xxxx') book_list = Book.objects.all() book_ser = BookSerializer(book_list,many=True) return Response(book_ser.data)
urls.py
path('books6/',views.Book6View.as_view(actions={'get':'get_all_book'}))
一切皆对象
def foo(a,b): return a+b foo.name = 'lqz' # 由于一切皆对象,函数也是个对象,对象放值 print(foo(2,3)) print(foo.name) print(foo.__name__)
局部禁用csrf
在视图函数上加装饰器 @csrf_exempt
csrf_exempt(view) 这么写和在视图函数上加装饰器是一模一样的
urs.py中看到这种写法
path('test/',csrf_exempt(views.test))
路由
在urls.py中配置
path('book4/',views.Book4View.as_view()) re_path('book4/(?P<pk>\d+)',views.Book4DetailView.as_view()
一旦视图类,继承了ViewSetMixin,路由
path('books5',views.Book5View.as_view(actions={'get':'list','post':'create'})),#当路径匹配,又个get请求,会执行Book5View的get方法 re_path('books5/(?P<pk>\d+)',views.Book5View.as_view(actions={'get':'retrieve','put':'update','delete':'destroy'})),
继承自ModelViewSet的路由写法(自动生成路由)
urls.py
# 第一步: 导入routers模块 from rest_framework import routers # 第二步: 有两个类, 实例化得到对象 # routers.DefaultRouter 生成的路由更多 # routers.SimpleRouter # router = routers.SimpleRouter() router = routers.DefaultRouter() # 第三步: 注册 # router.register('前缀','继承自ModelViewSet的视图类') router.register('books',views.BookViewSet) # books不要加斜杠了 # 第四步 # router.urls # 自动生成路由,加入到原路由 # print(router.urls) # ''' # [<URLPattern '^books/$' [name='book-list']>, <URLPattern '^books/(?P<pk>[^/.]+)/$' [name='book-detail']>] # # ''' urlpatterns = [ path('admin/', admin.site.urls), # path('books/',views.BookViewSet.as_view(actions = {'get':'list','post':'create'})), # re_path('books/(?P<pk>\d+)',views.BookViewSet.as_view(actions={'get':'retrieve','put':'update','delete':'destroy'})) ] urlpatterns += router.urls
views.py
from rest_framework.viewsets import ModelViewSet from app01.models import Book from app01.ser import BookSerializer # Create your views here. class BookViewSet(ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializer
action的使用
action干什么用?为了给继承自modelviewset的视图类中定义的函数也添加路由
使用
class BookViewSet(ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializer # method第一个参数,传一个列表,列表中放请求方式 # '^books/get_2/$ [name='book-get-2']' 当向这个地址发送get请求,会执行下面的函数 # detail:布尔类型,如果是True:'^books/(?P<pk>[^/.]+)/get_2/$ [name='book-get-2']' @action(methods=['GET','POST'],detail=True) def get_2(self,request,pk): print(pk) book = self.get_queryset()[:2] # 从0开始截取一条 ser = self.get_serializer(book,many=True) return Response(ser.data)
装饰器,放在被装饰的函数上麦那,两个参数 method: 请求方式,detail:是否带pk
认证的写法
认证的实现
1. 写一个类,继承BaseAuthentication,重写authenticate,认证的逻辑写在里面,认证通过返回两个值,一个值给了Request类的对象的user。认证失败,抛异常APIException或者AuthenticationFailed
2. 全局使用,局部使用
认证的源码实现
1. APIView ---> dispatch方法 ---> self.initial(request, *args, **kwargs)->有认证,权限,频率
2. 只读认证源码 self.perform_authentication(request)
3. self.perform_authentication(request)就一句话: request.user,需要去drf的Request对象中找user属性(方法)
4. Request中的user方法,刚开始来,没有_user,走self._authenticate()
5. 核心,就是Request类的_authenticate()方法
def _authenticate(self): # 遍历拿到一个个认证器,进行认证 # self.authenticators配置的一堆认证类产生的认证类对象组成的list # self.authenticators是你在视图类中配置的一个个的认证类:authentication_classes = [认证类1,认证类2],对象的列表 for authenticator in self.authenticators: try: # 认证器(对象)调用认证方法的authenticate(认证类对象self,request请求对象) # 返回值:登录的用户与认证的信息组成的tuple # 该方法被try包裹,代表方法会抛异常,抛异常就代表认证失败 user_auth_tuple = authenticator.authenticate(self) except exceptions.APIException: self._not_authenticated() raise # 返回值的处理 if user_auth_tuple is not None: self._authenticator = authenticator # 如果有返回值,就将登录用户与登录认证分别存到request.user,request.auth self.user, self.auth = user_auth_tuple return # 如果返回user_auth_tuple为空,代表认证通过,但是没有登录用户与登录认证信息,代表游客 self._not_authenticated()
认证组件的使用
写一个认证类
app_auth.py
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from app01.models import UserToken class MyAuthentication(BaseAuthentication): def authenticate(self, request): # 认证逻辑,如果认证通过,返回两个值 # 如果认证失败,抛出AuthenticationFailed token = request.GET.get('token') print(request.query_params) # print(token) if token: user_token = UserToken.objects.filter(token=token).first() # 认证通过 if user_token: return user_token.user,token # return '333',token else: raise AuthenticationFailed('认证失败') else: raise AuthenticationFailed('请求地址中需要携带token')
可以有多个认证,从左到右依次执行
全局使用,在settings.py中配置
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'app01.app_auth.MyAuthentication' ] }
局部使用,在视图类上写
authentication_classes = [MyAuthentication]
局部禁用
authentication_classes = [MyAuthentication]
权限
权限源码分析
# APIView -> dispatch -> initial --> self.check_permissions(request) APIView的
def check_permissions(self, request):
"""
Check if the request should be permitted.
Raises an appropriate exception if the request is not permitted.
"""
for permission in self.get_permissions(): # 权限类的对象,放到列表中
if not permission.has_permission(request, self):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
权限组件的使用
写一个类,继承BasePermission,重写has_permmission,如果权限公国,就返回True,不通过就返回False
class UserPermission(BasePermission): def has_permission(self, request, view): # 不是超级用户,不能访问 # 由于认证已经过了,request内就有user对象了,当前登录用户 user = request.user #当前登录用户 # 如果该字段用了choice,通过get_字段名_display()就能取出choice后面的中文名 print(user.get_user_type_display()) if user.user_type == 1: return True else: return False
局部使用
class TestView(APIView): authentication_classes = [app_auth.MyAuthentication] permission_classes = [app_auth.UserPermission] def get(self,request): return Response('这是测试数据')
全局使用 settings.py
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'app01.app_auth.MyAuthentication' ], 'DEFAULT_PERMISSION_CLASSES': [ 'app01.app_auth.UserPermission', ], }
局部禁用
放一个空列表
内置权限
内置权限的使用: IsAdminUser
1.创建超级管理员
2. 写一个测试视图类
from rest_framework.permissions import IsAdminUser from rest_framework.authentication import SessionAuthentication,BasicAuthentication class TestView3(APIView): authentication_classes = [SessionAuthentication] permission_classes = [IsAdminUser] def get(self,request): return Response('这是测试数据33333,超级管理员可以看')
3.超级用户登录到admin,再访问test3就有权限
频率组件
内置的频率限制限制未登录用户
全局使用 限制未登录用户1分钟访问5次
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'app01.app_auth.MyAuthentication' ], # 'DEFAULT_PERMISSION_CLASSES': [ # 'app01.app_auth.UserPermission', # ], 'DEFAULT_THROTTLE_CLASSES':['rest_framework.throttling.AnonRateThrottle'], 'DEFAULT_THROTTLE_RATES':{ 'anon':'3/m', } }
局部使用 views.py
from rest_framework.throttling import AnonRateThrottle class TestView5(APIView): authentication_classes = [] permission_classes = [] throttle_classes = [AnonRateThrottle] def get(self,request): return Response('我是未登录用户')
内置频率限制之限制登录用户的访问频次
需求:未登录用户1分钟访问5次,登录用户1分钟方为10次
再settings.py中配置
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'app01.app_auth.MyAuthentication', ], # 'DEFAULT_PERMISSION_CLASSES': [ # 'app01.app_auth.UserPermission', # ], 'DEFAULT_THROTTLE_CLASSES': ['rest_framework.throttling.AnonRateThrottle', 'rest_framework.throttling.UserRateThrottle' ], 'DEFAULT_THROTTLE_RATES': { 'anon': '5/m', 'user': '20/m', } }
局部配置:
在视图中配一个就行
过滤 Filtering
安装 pip install django-filter
注册: 在app中注册
全局配,或者局部配置 'DEFAULT_FILTER_BACKENDS':('django_filters.rest_framework.DjangoFilterBackend',),
视图类 (配置可以按照哪个字段来过滤)
class BookView(ListAPIView): authentication_classes = [] queryset = Book.objects.all() serializer_class = BookSerializer filterset_fields = ('name',)
排序
局部使用和全局使用
from rest_framework.generics import ListAPIView from rest_framework.filters import OrderingFilter from app01.models import Book from app01.ser import BookSerializer class Book2View(ListAPIView): authentication_classes = [] queryset = Book.objects.all() serializer_class = BookSerializer filter_backends = [OrderingFilter] filterset_fields = ('name','price','publish') orderingset_fields= ('id','price')
使用:
异常处理
统一接口返回
自定义异常方法,替换掉全局
写一个方法
自定义异常处理方法
from rest_framework.response import Response from rest_framework import status def my_exception_handler(exc, context): from rest_framework.views import exception_handler # print(exc) response = exception_handler(exc, context) # print(response.data) # 两种情况,一个是None, drf没有处理 # response对象,django处理了,但是处理的不符合咱们的要求 # if isinstance(exc,) if not response: if isinstance(exc,ZeroDivisionError): return Response(data={'status':777,'msg':'除以0的错误'+str(exc)},status=status.HTTP_400_BAD_REQUEST) return Response(data={'status':999,'msg':str(exc)},status=status.HTTP_400_BAD_REQUEST) else: return Response(data={'status':888,'msg':response.data.get('detail')},status=status.HTTP_400_BAD_REQUEST)
全局配置
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'app01.app_auth.MyAuthentication', ], # 'DEFAULT_PERMISSION_CLASSES': [ # 'app01.app_auth.UserPermission', # ], 'DEFAULT_THROTTLE_CLASSES': ['rest_framework.throttling.AnonRateThrottle', 'rest_framework.throttling.UserRateThrottle' ], 'DEFAULT_THROTTLE_RATES': { 'anon': '5/m', 'user': '20/m', }, 'DEFAULT_FILTER_BACKENDS':('django_filters.rest_framework.DjangoFilterBackend',), 'EXCEPTION_HANDLER': 'app01.app_auth.my_exception_handler', }
封装Response对象
以后都用自己封装的
class APIResponse(Response): def __init__(self, code=100, msg='成功', data=None, status=None, headers=None,*args,**kwargs): dic = {'code':code,'msg':msg} if data: dic = {'code':code,'msg':msg,'data':data} dic.update(kwargs) super().__init__(data=dic, status=status, headers=headers)
使用 views.py
from app01.app_auth import APIResponse class TestView7(APIView): authentication_classes = [] permission_classes = [] # throttle_classes = [AnonRateThrottle] def get(self,request): return APIResponse(data= {'name':'jeffrey'},token='ccc',aa = 'bbb')