Django之了解DRF框架
# 介绍:DRF全称 django rest framework
# 背景:
在序列化与反序列化时,虽然操作的数据不尽相同,但是执行的过程却是相似的,也就是说这部分代码是可以复用简化编写的
增:校验请求数据 -> 执行反序列化过程 -> 保存数据库 -> 将保存的对象序列化并返回
删:判断要删除的数据是否存在 -> 执行数据库删除
改:判断要修改的数据是否存在 -> 校验请求的数据 -> 执行反序列化过程 -> 保存数据库 -> 将保存的对象序列化并返回
查:查询数据库 -> 将数据序列化并返回
# 作用:Django REST framework可以帮助我们简化上述两部分的代码编写,大大提高REST API的开发速度# DRF特点:
1.提供了定义序列化器Serializer的方法,可以快速根据 Django ORM 或者其它库自动序列化/反序列化;
2.提供了丰富的类视图、Mixin扩展类,简化视图的编写;
3.丰富的定制层级:函数视图、类视图、视图集合到自动生成 API,满足各种需要;
4.多种身份认证和权限认证方式的支持;
5.内置了限流系统;
6.直观的 API web 界面;
7.可扩展性,插件丰富# 环境安装与配置:
1、安装DRF
pip install djangorestframework
2、在settings.py的INSTALLED_APPS中添加’rest_framework’
INSTALLED_APPS = [ ... 'rest_framework', ]
# DRF牛刀小试:
1、创建序列化器,在books应用中新建serializers.py用于保存该应用的序列化器,创建一个类用于序列化与反序列化:
from rest_framework import serializers from books.models import Book class BookInfoSerializer(serializers.ModelSerializer): """图书数据序列化器""" class Meta: model = Book fields = '__all__'
model 指明该序列化器处理的数据字段从模型类Book参考生成
fields 指明该序列化器包含模型类中的哪些字段,'all’指明包含所有字段2、编写视图,在books应用的views.py中创建视图BookViewSet,这是一个视图集合:
from rest_framework.viewsets import ModelViewSet from .serializers import BookSerializer from .models import Book class BookViewSet(ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializer
queryset 指明该视图集在查询数据时使用的查询集
serializer_class 指明该视图在进行序列化或反序列化时使用的序列化器3、定义路由,在books应用的urls.py中定义路由信息
from . import views from rest_framework.routers import DefaultRouter router = DefaultRouter() # 可以处理视图的路由器 router.register(r'books', views.BookViewSet, 'books') # 向路由器中注册视图集 urlpatterns = [ ] urlpatterns += router.urls # 将路由器中的所以路由信息追到到django的路由列表中
三大认证组件回顾
# 认证类的编写
用户登录后,会有登录标识,早期直接写入到浏览器中了session['name']='lqz' obj.set_cookie()
写认证类的目的:校验它携带的登录标识,是否合法,是否在库中存着
只需要按照固定规则去写即可:
1、写一个类,继承 BaseAuthentication
2、重写 authenticate 方法
3、在方法中完成校验--》用户标识用户标识带在请求头 / 请求地址 / 请求体
校验携带的标识正不正确---> 去数据库查
查到返回俩值:第一个值会给后续的request.user,第二个值给request.auth
校验失败:抛异常class LoginAuth(BaseAuthentication): # 不一定继承BaseAuthentication def authenticate(self, request): # 目的是重写authenticate,有authenticate就是认证类 token = request.query_params.get('token') or request.META.get('HTTP_TOKEN') user_token = UserToken.objects.filter(token=token).first() if user_token: user = user_token.user # 重写是因为源码里在一个个调用这个方法,在方法中完成校验 return user, user_token # 返回两个值,第一个值会给request.user,第二个给了request.auth else: # 标识一般带到请求头、请求地址和请求体中,由后端决定 raise AuthenticationFailed("您没有登录")
4、放在视图类上,局部禁用:
authentication_classes=[ ]
5、放在配置文件中--drf配置文件中找 :REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ['app01.auth.LoginAuth'], }
# 权限类
用户登录了,有没有权限访问接口
权限放在了认证之后,request.user :当前登录用户,根据是谁来做权限
编写规则:
1、写一个类类,继承 BasePermission
2、 重写 has_permission 方法
3、在方法中完成权限校验--》当前登录用户
能有用户,就能取出用户的权限
如果有,返回True,没有返回False
前端看到中文提示:self.messageclass CommonPermission(BasePermission): def has_permission(self, request, view): try: user_type = request.user.user_type if user_type == 2: return True else: # user.get_字段名_display() 返回这个字段的choice对应的文字 self.message = '您是:%s,您没有权限操作这个' % request.user.get_user_type_display() return False except Exception as e: # 未登录用户也没有权限 if 'login' in request.path: return True else: self.message = '您没有登录,您没有权限操作这个' return False
4、放在视图类上(可以配多个),局部禁用:
permission_classes=[ ]
5、放在配置文件中--drf配置文件中找:REST_FRAMEWORK = { 'DEFAULT_permission_CLASSES': [''], }
# 频率类使用
限制用户访问频次:用户登录或不登录都可能会限制
限制条件:ip,用户id
编写步骤:
1、写一个类类,继承 SimpleRateThrottle
2、重写 get_cache_key 方法
返回什么就以什么做限制:ip或id号
类上写个属性:rate = '5/d' # s m h dclass CommonThrottle(SimpleRateThrottle): rate = '3/m' def get_cache_key(self, request, view): ip = request.META.get('REMOTE_ADDR') return ip
3、放在视图类上(可以配多个),局部禁用:
throttle_classes=[ ]
4、 放在配置文件中--drf配置文件中找:REST_FRAMEWORK = { 'DEFAULT_throttle_CLASSES': [''], }
排序之继承GenericAPIView
# 1 只有查询所有需要排序
# 2 如何使用
1 必须是继承 GenericAPIView 及其子类
2 在类中配置类属性
filter_backends = [OrderingFilter]
3 类中写属性
ordering_fields = ['price','id'] # 必须表的字段
4 以后再前端,就可以访问
http://127.0.0.1:8000/api/v1/books/?ordering=price 按price升序排
http://127.0.0.1:8000/api/v1/books/?ordering=-price 按price降序排
http://127.0.0.1:8000/api/v1/books/?ordering=-price,idfrom .models import Book from rest_framework.viewsets import ViewSetMixin from rest_framework.generics import ListAPIView from .serializer import BookSerializer # 排序之继承 GenericAPIView from rest_framework.filters import OrderingFilter class BookView(ViewSetMixin,ListAPIView): # 改路径和查询所有 queryset = Book.objects.all() serializer_class = BookSerializer # 序列类映射 filter_backends = [OrderingFilter] # 排序 ordering_fields = ['price','id'] # http://127.0.0.1:8000/api/v1/books/?ordering=-price,id
排序之继承APIview
# 如果继承APIView,过滤规则自己写
# 单条排序:
# 排序之继承 APIView from rest_framework.views import APIView from rest_framework.response import Response class BookView(ViewSetMixin, APIView): def list(self, request): # 从地址兰中取出用户过滤条件 # http://127.0.0.1:7000/api/v1/books/?ordering=-price query_params = request.query_params #< QueryDict: {'ordering': ['-price']}> obj_list = Book.objects.all().order_by(query_params.get('ordering')) ser = BookSerializer(instance=obj_list, many=True) return Response(ser.data)
# 多条排序:
# 排序之继承 APIView from rest_framework.views import APIView from rest_framework.response import Response class BookView(ViewSetMixin, APIView): def list(self, request): # 从地址兰中取出用户过滤条件 # http://127.0.0.1:7000/api/v1/books/?ordering=-price,id query_params = request.query_params #<QueryDict: {'ordering': ['-price,id']}> print(query_params) if ',' in query_params.get('ordering'): query = query_params.get('ordering').split(',') obj_list = Book.objects.all().order_by(*query) ser = BookSerializer(instance=obj_list, many=True) return Response(ser.data)
过滤之使用drf内置过滤类
# restful规范中有一条是请求地址中带过滤条件,查询所有数据,只查询出我们想要的
# 如何使用
1 必须是继承 GenericAPIView 及其子类
2 在类中配置类属性
filter_backends = [SearchFilter]
3 类中写属性
search_fields = ['price','id'] # 必须表的字段
4 以后再前端,就可以访问
http://127.0.0.1:7000/api/v1/books/?search=风http://127.0.0.1:7000/api/v1/books/?search=摆
# 过滤之使用drf内置过滤类 from rest_framework.filters import SearchFilter class BookView(ViewSetMixin,ListAPIView): # 改路径和查询所有 queryset = Book.objects.all() serializer_class = BookSerializer # 序列类映射 filter_backends = [SearchFilter] # 排序 search_fields = ['name'] # http://127.0.0.1:7000/api/v1/books/?search=风
过滤之使用djagno-filter过滤
# 下载 pip3 install django-filter
# 导入 from django_filters.rest_framework import DjangoFilterBackend
# 3 使用# 过滤之使用djagno-filter过滤 from django_filters.rest_framework import DjangoFilterBackend class BookView(ViewSetMixin,ListAPIView): # 改路径和查询所有 queryset = Book.objects.all() serializer_class = BookSerializer # 序列类映射 filter_backends = [DjangoFilterBackend] # 排序 filterset_fields = ['name','price'] # http://127.0.0.1:7000/api/v1/books/?name=摆渡人&price=34 # 查询书名为摆渡人且价格为34的
过滤之自定制过滤类
# 写个类,继承BaseFilterBackend重写filter_queryset,在内部完成过滤
# filter from rest_framework.filters import BaseFilterBackend class CommonFilter(BaseFilterBackend): def filter_queryset(self, request, queryset, view): # queryset 是之前所有数据 Book.object.all() # request 当次请求request name = request.query_params.get('name') price = request.query_params.get('price') if name: queryset = queryset.filter(name__contains=name) if price: queryset = queryset.filter(price=price) return queryset
# views.py # 过滤之自定制过滤类,重写了类里方法,再引用 from .filter import CommonFilter class BookView(ViewSetMixin,ListAPIView): # 改路径和查询所有 queryset = Book.objects.all() serializer_class = BookSerializer # 序列类映射 filter_backends = [CommonFilter] # 排序 # http://127.0.0.1:7000/api/v1/books/?name=追&price=31.2 # 书名用了模糊查法,价格是精准查法
自定义过滤+排序
# 自定义过滤+排序 from .filter import CommonFilter from rest_framework.filters import OrderingFilter class BookView(ViewSetMixin,ListAPIView): # 改路径和查询所有 queryset = Book.objects.all() serializer_class = BookSerializer # 序列类映射 filter_backends = [CommonFilter,OrderingFilter] # 过滤,排序 ordering_fields = ['price'] # http://127.0.0.1:7000/api/v1/books/?name=渡&ordering=-price # 查询书名含渡的且按照价格降序
分页之基础分页
# 查询所有接口,分页
# 使用drf内置了三种分页方式:
PageNumberPagination, LimitOffsetPagination, CursorPagination
1、写个类,继承基础分页类,重写类属性
# pagination.py 基本分页PageNumberPagination from rest_framework.pagination import PageNumberPagination class CommonPageNumberPagination(PageNumberPagination): page_size = 1 # 默认显示,每页大小,一页显示多少条 page_query_param = 'page' # 分页查询条件 ?page=1 page_size_query_param = 'size' #每页最多显示多少条的查询条件 &size=2 max_page_size = 3 # 每页最多显示多少条 # http://127.0.0.1:7000/api/v1/books/?search=34&size=2 # 过滤查询价格为34且一页显示2条数据的(不写size默认显示1条)
2、在继承自 GenericAPIView及其子类, 在视图类配置分页类即可
# 分页之基础分页 # 在内置过滤器的条件下分页 from rest_framework.filters import SearchFilter from .pagination import CommonPageNumberPagination class BookView(ViewSetMixin,ListAPIView): # 改路径和查询所有 queryset = Book.objects.all() serializer_class = BookSerializer # 序列类映射 filter_backends = [SearchFilter] # 过滤,排序 search_fields = ['price'] pagination_class = CommonPageNumberPagination
分页之偏移分页
# 查询所有接口,分页
# 使用drf内置了三种分页方式:
PageNumberPagination, LimitOffsetPagination, CursorPagination
1、写个类,继承基础分页类,重写类属性
# pagination.py 基本分页LimitOffsetPagination from rest_framework.pagination import LimitOffsetPagination class CommonLimitOffsetPagination(LimitOffsetPagination): default_limit = 1 # 默认显示,每页显示的条数 limit_query_param = 'limit' # 分页查询条件 ?limit=1 offset_query_param = 'offset' # # 偏移量 从第2条开始,拿2条 offset=2&limit=2 max_limit = 3 # # 每页最多显示多少条 # http://127.0.0.1:7000/api/v1/books/?limit=1&page=3 显示第三页的一条数据 # http://127.0.0.1:7000/api/v1/books/?offset=1&limit=2 从第一条数据开始显示2条数据
2、在继承自 GenericAPIView及其子类, 在视图类配置分页类即可
# 分页之基础分页 # 在内置过滤器的条件下分页 from rest_framework.filters import SearchFilter from .pagination import CommonPageNumberPagination from .pagination import CommonLimitOffsetPagination class BookView(ViewSetMixin,ListAPIView): # 改路径和查询所有 queryset = Book.objects.all() serializer_class = BookSerializer # 序列类映射 filter_backends = [SearchFilter] # 过滤,排序 search_fields = ['price'] pagination_class = CommonPageNumberPagination pagination_class = CommonLimitOffsetPagination
分页之游标分页
# 特点:只能从第一页开始,只能上一页和下一页,不能自定义哪页,但是效率高很多
1、写个类,继承基础分页类,重写类属性
# pagination.py 游标分页 CursorPagination from rest_framework.pagination import CursorPagination class CommonCursorPagination(CursorPagination): cursor_query_param = 'cursor' # 分页查询条件 ?cursor=1 page_size = 2 # 每页显示的条数 ordering = 'id' # 排序规则,必须是表中字段 # http://127.0.0.1:7000/api/v1/books/ # 内部已经写死,从第一页查起,显示2条,只能上下翻页
2、在继承自 GenericAPIView及其子类, 在视图类配置分页类即可
# views.py # 分页之基础分页 # 在内置过滤器的条件下分页 from rest_framework.filters import SearchFilter from .pagination import CommonPageNumberPagination from .pagination import CommonLimitOffsetPagination from .pagination import CommonCursorPagination class BookView(ViewSetMixin,ListAPIView): # 改路径和查询所有 queryset = Book.objects.all() serializer_class = BookSerializer # 序列类映射 filter_backends = [SearchFilter] # 过滤,排序 search_fields = ['price'] pagination_class = CommonPageNumberPagination pagination_class = CommonLimitOffsetPagination pagination_class = CommonCursorPagination
全局异常处理
# 三大认证和视图类的方法中出现了异常,都会被try捕获,做全局异常处理
# 捕获到统一处理,无论我是否出异常,返回的格式都是固定的
{code:100,msg:成功}
# 新建py文件,写一个函数,在函数中处理异常# exceptions.py # drf的异常处理是exception_handler处理,没处理非drf的异常 # 自己写个函数,处理drf异常和自己的异常 from rest_framework.views import exception_handler from rest_framework.response import Response def common_exception_handler(exc, context): res = exception_handler(exc, context) # 处理drf异常处理 if res: # data = {'detail': exc.detail},错误信息 # return Response(data) # {code: 100, msg: 成功} detail = res.data.get('detil') or "drf异常,请联系系统管理员" return Response({'code': 101, 'msg': detail}) else: return Response({'code': 102, 'msg': '系统异常,请联系系统管理员:%s'%str(exc)})
# 把函数配置在配置文件中,setting.py
REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'app01.exceptions.common_exception_handler', }
# 只要三大认证或视图类的方法出了异常,就会走这个函数
# 全局异常处理 # 无论我是否出异常,返回的格式都是固定的 from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.exceptions import AuthenticationFailed,ValidationError,APIException class UserView(ViewSetMixin, APIView): def create(self, request): # post--->create # l = [1, 2, 3] # print(l[9]) # raise Exception("你出错了") # drf 默认能处理自己的异常:继承自APIException的异常 raise APIException('认证失败') return Response({'code': 100, 'msg': '成功'})