# throttling 频率限制 # 简单方案 from rest_framework.throttling import SimpleRateThrottle class CommonThrottle(SimpleRateThrottle): rate = '3/m' def get_cache_key(self, request, view): ip = request.META.get('REMOTE_ADDR') return ip # 复杂方案---》通用方案---》后期任何框架都可以使用这个逻辑 from rest_framework.throttling import BaseThrottle import time class MyThrottling(BaseThrottle): VISIT_RECORD = {} # {192.168.1.11:[时间2,时间1],192.168.1.99:[时间3,时间2,时间1]} def __init__(self): self.history = None def allow_request(self, request, view): ip = request.META.get('REMOTE_ADDR') # 取出访问者ip ctime = time.time() if ip not in self.VISIT_RECORD: self.VISIT_RECORD[ip] = [ctime, ] return True self.history = self.VISIT_RECORD.get(ip) # 访问时间列表 while self.history and ctime - self.history[-1] > 3600: self.history.pop() if len(self.history) < 5: self.history.insert(0, ctime) return True else: return False def wait(self): ctime = time.time() return 60 - (ctime - self.history[-1])
# exceptions 异常处理 自己写个函数,处理drf异常和自己的异常 from rest_framework.views import exception_handler from rest_framework.response import Response from rest_framework.exceptions import Throttled def common_exception_handler(exc, context): res = exception_handler(exc, context) # 处理drf异常处理 if res: if isinstance(exc, Throttled): return Response({'code': 20016, 'msg': '使用过于频繁,稍后再试:%s' % res.data.get('detail')}) else: detail = res.data.get('detail') or res.data or "drf异常,请联系系统管理员" return Response({'code': 999, 'msg': detail}) # return res else: # 如果没值,说明是自己的异常 # exc 错误对象,判断具体是什么错误 :数据错误, 除以0,。。。。 print(type(exc)) if isinstance(exc, ZeroDivisionError): return Response({'code': 20001, 'msg': '不能除以0'}) if isinstance(exc, IndexError): return Response({'code': 20002, 'msg': '超长了'}) else: return Response({'code': 888, 'msg': '操作失败,请稍后再试:%s' % str(exc)})
# common 更改显示格式 from rest_framework.mixins import ListModelMixin from rest_framework.response import Response class CommonListModelMixin(ListModelMixin): def list(self, request, *args, **kwargs): res = super().list(request, *args, **kwargs) return Response({'code': 100, 'msg': '查询所有成功', 'results': res.data})
# serializer.py 序列化类 from rest_framework import serializers from .models import Book class BookSerializer(serializers.ModelSerializer): class Meta: model = Book fields = ['id', 'name', 'price', 'publish', 'authors', 'publish_detail', 'author_list'] extra_kwargs = { 'publish': {'write_only': True}, 'authors': {'write_only': True}, 'publish_detail': {'read_only': True}, 'author_list': {'read_only': True}, }
# views.py 视图层 # 查询所有图书 from .models import Book from .serializer import BookSerializer from .common import CommonListModelMixin as ListModelMixin from rest_framework.viewsets import GenericViewSet from .throttling import MyThrottling class BookView(GenericViewSet, ListModelMixin): queryset = Book.objects.all() serializer_class = BookSerializer # throttle_classes = [CommonThrottling] throttle_classes = [MyThrottling]
# urls 路由 from . import views from rest_framework.routers import SimpleRouter, DefaultRouter router = SimpleRouter() router.register('books', views.BookView, 'books') urlpatterns = [ ] urlpatterns += router.urls # 总路由 from django.contrib import admin from django.urls import path,include urlpatterns = [ path('admin/', admin.site.urls), path('api/v1/', include('app01.urls')), ]
# 1 APIView--->dispatch--->self.initial(request, *args, **kwargs)--->self.check_throttles(request) # 2 APIView的check_throttles def check_throttles(self, request): throttle_durations = [] # self.get_throttles() 是视图类中配置的一个个频率类的对象列表 for throttle in self.get_throttles(): # throttle.allow_request 返回false ,说明到频率了 if not throttle.allow_request(request, self): # 到了频率,走了它 # throttle.wait() 返回还剩多长时间能访问 throttle_durations.append(throttle.wait()) # 被频率限制住了,它就有值 if throttle_durations: durations = [ duration for duration in throttle_durations if duration is not None ] #duration=[35,54] # duration=[] duration = max(durations, default=None) # duration=56 # duration=None self.throttled(request, duration) # 3 self.throttled(request, duration) ---》APIView def throttled(self, request, wait): # wait=56或None raise exceptions.Throttled(wait) # 4 Throttled类实例化得到对象,传了数字进去 from rest_framework.exceptions import Throttled 内部 拼接错误信息--》但是是英文的,如果要换成中文 # 5 超过了频率,就不返回False了,直接抛异常 raise Throttled(None, '超过了限制,还剩:%s 时间' % self.wait())
# 1 咱们写的 CommonThrottling没有写allow_request class CommonThrottling(SimpleRateThrottle): rate = '3/m' def get_cache_key(self, request, view): return request.META.get('REMOTE_ADDR') # 2 一定是 SimpleRateThrottle 写了,完成了频率校验 # 3 SimpleRateThrottle--》allow_request---》跟咱们的逻辑一样 def allow_request(self, request, view): if self.rate is None: # 自己写了 '3/m' return True # 返回了 ip地址,self.key 就是访问者的ip地址 self.key = self.get_cache_key(request, view) if self.key is None: return True # self.history 当前ip,访问的时间列表,self.key是ip # self.cache 缓存,去缓中,根据ip,取出访问者时间列表,如果没有就是 [] self.history = self.cache.get(self.key, []) # 当前时间 self.now = self.timer() # timer = time.time # self.now 当前时间, # self.duration 就是60 while self.history and self.history[-1] <= self.now - self.duration: self.history.pop() # self.num_requests 是3 if len(self.history) >= self.num_requests: return self.throttle_failure() return self.throttle_success() # 4 self.duration 就是60 self.num_requests 是3 # 3 60 self.num_requests, self.duration = self.parse_rate(self.rate) #5 self.parse_rate(self.rate) def parse_rate(self, rate): if rate is None: # '3/minute' return (None, None) # 3 period=mmmmm num, period = rate.split('/') # 3 num_requests = int(num) duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]] # duration 是60, # num_requests 是3 return (num_requests, duration)
# 前后端开发模式:混合开发(不分离),前后端分离
# API接口
# 接口测试:postman
测试:接口测试: jmeter是java开发的软件
# restful规范:10条规范
drf 帮助我们快速实现符合restful规范的接口
# APIView执行流程:重写了as_view,重写了dispatch
1、去除了csrf认证
2、包装了新的request
3、三大认证 --根据请求方式,执行视图类的方法(认证组件、权限组件、频率组件)
4、处理了全局异常
# Request 类的对象
1、多了data
2、重写了 __getattr__ 对象.属性触发
去旧的request中找:通过反射 getattr(self._request,'path')
3、以后用起来跟之前一样
4、query_params
# 序列化:
1、写一个类,继承(序列化类)
2、写字段,需要跟表中有对应关系(手动对应:Serializer 自动对应:ModelSerializer)
3、视图类中使用:# 多条Queryset对象:使用orm查询出来的 # django执行原生sql-->使用序列化类完成序列化 ser=BookSerializer(instnce=qs,many=True) ser.data+Response
# 反序列化:
1、写一个类,继承(序列化类)
2、写字段,需要跟表中有对应关系(手动: Serializer 自动:ModelSerializer)
3、视图类中使用修改ser=BookSerializer(instance=对象,data=request.data)
新增ser=BookSerializer(data=request.data)
ser.is_valid()
ser.save()--->触发序列化类的:update,create# 序列化和反序列化用在一起:
read_only 和 write_only
extra_kwargs={ }# 校验:三层
Serializer-重写 update和create
ModelSerializer-重写字段-class Meta: model 、fields 、extra_kwargsclass BookSerializer(serializers.Serializer): # 手动 name = serializers.CharField() # 公共的 price = serializers.CharField() # 公共的 publish = serializers.IntegerField(write_only=True) # 只用来做反序列化 authors = serializers.ListField(write_only=True) # 只用来做反序列化 publish_detail = serializers.SerializerMethodField(read_only=True) # 只用来做序列化 author_list = serializers.SerializerMethodField(read_only=True) # 只用来做序列化 def get_publish_detail(self, obj): return {'name': obj.publish.name, 'city': obj.publish.city} def get_author_list(self, obj): l = [] for author in obj.authors.all(): l.append({'name': author.name, 'age': author.age}) return l def create(self, validated_data): # {name,price,publish:1,authors:[1,2]} authors = validated_data.pop('authors') book = Book.objects.create(name=validated_data.get('name'), price=validated_data.get('price'),publish_id=validated_data.get('publish')) book.authors.add(*authors) return book def update(self, instance, validated_data): # {name,price,publish:1,authors:[1,2]} authors = validated_data.pop('authors') validated_data['publish_id'] = validated_data.pop('publish') for key in validated_data: setattr(instance, key, validated_data[key]) instance.save() # 先清空在放入 # instance.authors.clear() # instance.authors.add(*authors) # 直接存 instance.authors.set(authors) return instance
from rest_framework import serializers from .models import Book class BookSerializer(serializers.ModelSerializer): class Meta: model = Book fields = ['id', 'name', 'price', 'publish', 'authors', 'publish_detail', 'author_list'] extra_kwargs = { 'publish': {'write_only': True}, 'authors': {'write_only': True}, 'publish_detail': {'read_only': True}, 'author_list': {'read_only': True}, }
# source定制:表中字段,表中方法,跨表
#1 修改字段,映射字段:
publish_name表中不存在
publish_name = serializers.CharField(source='name')#2 修改字段,映射方法:
sb_name是表模型中一个方法
name = serializers.CharField(source='sb_name')#3 修改字段,跨表查询
book表中可以链表查询
publish=models.ForeignKey(to='Publish.name')# 定制返回格式:序列化
表模型中写方法
序列化类中:SerializerMethod--》get_字段名)# models.py from django.db import models class Book(models.Model): name = models.CharField(max_length=32) price = models.DecimalField(max_digits=5, decimal_places=2) publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE) authors = models.ManyToManyField(to='Author') def __str__(self): return self.name # def book_name(self): # return self.name+'sb' # def publish_detail(self): # return {'name': self.publish.name, 'city': self.publish.city} # # def author_list(self): # l = [] # for author in self.authors.all(): # l.append({'name': author.name, 'age': author.age})
# serializer.py class BookSerializer(serializers.Serializer): name = serializers.CharField() price = serializers.CharField() #方案一:在表模型中写方法,在序列化类中做映射 # publish_detail = serializers.DictField() # publish_detail 会映射表模型中 publish_detail方法,方法返回值是 字典,用DictField接收 # author_list = serializers.ListField() #方案二:在序列化类中写 SerializerMethodField # 只要写了这个字段类SerializerMethodField,必须配合一个方法:get_字段名,这个方法返回什么,前端这个字段就显示什么 publish_detail = serializers.SerializerMethodField() def get_publish_detail(self, obj): return {'name': obj.publish.name, 'city': obj.publish.city} author_list = serializers.SerializerMethodField() def get_author_list(self, obj): l = [] for author in obj.authors.all(): l.append({'name': author.name, 'age': author.age}) return l
# BookReadSerializer
BookWriteSerializer
LoginSerializerget_serializer_class()--影响:list retrieve update create 自己写的
get_queryset()--影响:list retrieve update create delete
get_object--影响:list,retrieve,update,delete,不会影响到create
class BookView(ModelViewSet): query_set=xx serializer_class=BookReadSerializer def get_serializer_class(self): # 重写 get_serializer_class 方法 if self.action=='list' or self.action=='retrieve': # if self.request.method=='get': # get_serializer() 获取使用哪个序列化类 return BookReadSerializer elif self.action=='login': return LoginSerializer else: return BookWriteSerializer def get_queryset(self):pass # 返回所有数据,但会影响 list,retrieve,update,delete ,自己写的 @action(methods=['POST'],detail=False) def login(self,requset): self.get_serializer() #让它拿到 LoginSerializer self.get_object() self.get_queryset
# 请求:
Request源码
能够解析的编码格式:默认三种:json,urlencoded,form-data1、视图类上配置
2、settings.py 配置文件中配置
from rest_framework.parsers import JSONParser, FormParser, MultiPartParser # JSONParser:json # FormParser:urlencoded # MultiPartParser:form-data class TestView(APIView): # parser_classes = [JSONParser] parser_classes = [JSONParser,FormParser] def post(self, request): print(request.data) return Response('ok')
setting.py # 所有drf的配置,都要写在REST_FRAMEWORK 字典中 REST_FRAMEWORK = { 'DEFAULT_PARSER_CLASSES': [ # 'rest_framework.parsers.JSONParser', # 'rest_framework.parsers.FormParser', 'rest_framework.parsers.MultiPartParser', ], } views.py class TestView(APIView): parser_classes = [JSONParser,FormParser]
# 响应:
*Response源码
*实例化时可传入的参数:data=None----响应体 status=None---http响应状态码 import status 状态码 headers=None---响应头 content_type=None---响应编码格式 list方法: res=super().list(request) # res 就是Response 类的对象 res.status res.data res.headers res.data [{},{},{}] {code:100,msg:成功,results:res.data} # 响应编码格式
# APIView:
1、继承了View
2、重写了 as_view
3、重写了 dipatch---相当于给之前的视图类的方法加了装饰器
4、写跟请求方式同名的方法:get,post,delete,put,get
# GenericAPIView:
*继承了APIView*类属性: queryset =Book.object.all().filter(is_delete=False) # self.get_queryset拿到要序列化的数据 lookup_field = 'pk' # 分组,转换器出来的参数 filter_backends # 过滤和排序 pagination_class # 分页
*方法: get_serializer_class # 通过重写控制视图类的方法使用哪个序列化类 serializer_class # 获取序列化类,list,retrieve(get_object间接),put,create get_queryset # 要序列化的总数据或单条查询的数据源 get_object # 获取单条,通过get_queryset拿到的 filter_queryset # 过滤使用,必须配合list使用,视图类中重写它可以完成过滤,就不用配置过滤类了 get_paginated_response # 获取分页后的返回数据,必须配合list def get_paginated_response(self, data): assert self.paginator is not None return Response({ 'code': 100, 'msg': 'asdfasfasfdasf', 'next': self.paginator.get_next_link(), 'pre': self.paginator.get_previous_link(), 'result': data})
# 5个视图扩展类--必须搭配GenericAPIView
ListModelMixin # 过滤,排序,分页 def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) page = self.paginate_queryset(queryset) # 处理了分页 if page is not None: # 如果没有分页,正常返回 serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True) return Response(data=serializer.data) RetrieveModelMixin def retrieve(self, request, *args, **kwargs): instance = self.get_object() # 序列化单挑数据--》重写 serializer = self.get_serializer(instance) # 重写--》使用哪个序列化类 return Response(serializer.data) CreateModelMixin def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) # 重写 serializer.is_valid(raise_exception=True) self.perform_create(serializer) #做了真正的保存serializer.save(),重写 return Response(serializer.data, status=status.HTTP_201_CREATED) def perform_create(self, serializer): #可以重写 serializer.save() DestroyModelMixin def destroy(self, request, *args, **kwargs): instance = self.get_object() # 通过重写它,决定删除谁 self.perform_destroy(instance) # 重写-软删除,不在数据库删除,is_delete # 设置为True,以后要序列化的时候,不查出来了 return Response(status=status.HTTP_204_NO_CONTENT) def perform_destroy(self, instance): instance.delete() UpdateModelMixin def update(self, request, *args, **kwargs): instance = self.get_object() serializer = self.get_serializer(instance, data=request.data) serializer.is_valid(raise_exception=True) self.perform_update(serializer) return Response(serializer.data) def perform_update(self, serializer): serializer.save()
# views.py from rest_framework.mixins import CreateModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin, \ DestroyModelMixin class BookView(GenericAPIView, CreateModelMixin, ListModelMixin): queryset = Book.objects.all() serializer_class = BookSerializer def get(self, request): return super().list(request) def post(self, request): # 做保存,加了这一句---》目的是:子类可以重写,增强扩展性 # self.perform_create(serializer) return super().create(request) class BookDetailView(GenericAPIView, RetrieveModelMixin, DestroyModelMixin, UpdateModelMixin): queryset = Book.objects.all() serializer_class = BookSerializer def put(self, request, *args, **kwargs): return super().update(request, *args, **kwargs) def get(self, request, *args, **kwargs): return super().retrieve(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return super().destroy(request, *args, **kwargs)
# 9 个视图子类--重写 get_serializer_class,get_queryset,get_object
ListAPIView:GenericAPIView+ListModelMixin # 重写:list,get RetrieveAPIView # 重写retrieve,get CreateAPIView # 重写create,perform_create UpdateAPIView DestroyAPIView ListCreateAPIView RetrieveUpdateDestroyAPIView, RetrieveDestroyAPIView, RetrieveUpdateAPIView
# views.py from rest_framework.generics import CreateAPIView, ListAPIView from rest_framework.generics import RetrieveAPIView, DestroyAPIView, UpdateAPIView from rest_framework.generics import ListCreateAPIView from rest_framework.generics import RetrieveUpdateDestroyAPIView, RetrieveDestroyAPIView, RetrieveUpdateAPIView # from rest_framework.generics import DestroyUpdateAPIView # 一般不存在,所以就没有 # 实现新增,查所有和查询一条 class BookView(ListCreateAPIView): # 配置两个类属性 queryset = Book.objects.all() serializer_class = BookSerializer class BookDetailView(RetrieveAPIView): queryset = Book.objects.all() serializer_class = BookSerializer
# 视图集
以后写视图类: 一般不会5个都写
比如只想写 查询所有和删除一条--继承--》GenericViewSet,ListModelMixinViewSetMixin # 路由写法变了---》多个视图类可以放到一起---》一个视图类中可以放多个方法 GenericViewSet ViewSet ModelViewSet # 有get,post这些方法吗?没有,只有list,create。。这些 # 重写:get_serializer_class,get_queryset,perform_destroy # 重写list,分页返回格式 ReadOnlyModelViewSet:只有 list,retrieve
# 视图类没有继承了ViewSetMixin,路由写法跟之前一样
path('books/', views.BookView.as_view())# 映射写法:ViewSetMixin
SimperRouter和DefaultRouter 实例化
from rest_framework.routers import SimpleRouter,DefaultRouter router = SimpleRouter() # router = DefaultRouter() router.register('books', views.BookView, 'books') urlpatterns = [ # path('', include(router.urls)), ] urlpatterns += router.urls
# 视图类中自己的方法,再做映射--action装饰器
action装饰器:methods,detail
@action(methods=['POST'],detail=False,) def login(self,request): return Response('login')
# 认证类的使用
# 权限类:
登录成功后,有没有权限---》request.user拿到登录用户,判断权限
# 频率类:
ip,用户id
用户访问记录:存在缓存中,默认在内存# auth.py 认证类 from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from .models import UserToken class LoginAuth(BaseAuthentication): def authenticate(self, request): 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 else: raise AuthenticationFailed("您没有登录")
# permission.py 权限类 from rest_framework.permissions import BasePermission class CommonPermission(BasePermission): def has_permission(self, request, view): try: user_type = request.user.user_type if user_type == 2: # 只有超级管理员能访问,其他人都不能访问 return True else: 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
# throttling.py 频率类 from rest_framework.throttling import SimpleRateThrottle class CommonThrottle(SimpleRateThrottle): rate = '3/m' def get_cache_key(self, request, view): ip = request.META.get('REMOTE_ADDR') return ip
# setting.py REST_FRAMEWORK = { # 全局配置登录认证 'DEFAULT_AUTHENTICATION_CLASSES': ['app01.auth.LoginAuth'], # 认证组件 'DEFAULT_PERMISSION_CLASSES': ['app01.permission.CommonPermission'], # 权限组件 'DEFAULT_THROTTLE_CLASSES': ['app01.throttling.CommonThrottle'], # 频率组件 }
# views.py 登录入口 1、拿到验证码 class UserView(ViewSet): authentication_classes = [] permission_classes = [] throttle_classes = [] @action(methods=['POST'], detail=False) def login(self, request): username = request.data.get('username') password = request.data.get('password') user = User.objects.filter(name=username, password=password).first() if user: token = str(uuid.uuid4()) UserToken.objects.update_or_create(defaults={'token': token}, user_id=user.pk) return Response({'code': '100', 'msg': '登录成功', 'token': token}) else: return Response({'code': '101', 'msg': '用户名或密码错误'}) class BookView(ViewSetMixin, ListCreateAPIView): authentication_classes = [LoginAuth] # 验证是否登录 queryset = Book.objects.all() serializer_class = BookSerializer class BookDetailView(GenericViewSet, DestroyModelMixin, RetrieveModelMixin, UpdateModelMixin): queryset = Book.objects.all() serializer_class = BookSerializer
-排序:内置排序 list接口,继承GenericAPIView+list
-filter_backends 配置 排序类
-ordering=id,-price
-过滤:
-内置
-第三方
-自定义:写个类,继承:BaseFilterBackend,重写filter_queryset在内部完成过滤,返回过滤后的qs对象
-过滤,排序可以连用,多个过滤也可以连用- list接口+GenericAPIView 配置分页类
- 三种分页方式
-都有自己的类属性
-继承APIView实现分页
from rest_framework.viewsets import ViewSet
from rest_framework.mixins import ListModelMixin,RetrieveModelMixin
from rest_framework.generics import GenericAPIView
from . import pagination
class BookView(ViewSet):
def list(self, request):
book_list = Book.objects.all()
# 调用咱们写的分页类对象的paginate_queryset方法返回了 分页后的qs对象
# pagenation = pagination.CommonPageNumberPagination()
# pagenation = pagination.CommonLimitOffsetPagination()
pagenation = pagination.CommonCursorPagination()
page = pagenation.paginate_queryset(book_list, request, self)
ser = BookSerializer(instance=page, many=True)
# return pagenation.get_paginated_response(ser.data)
# CommonPageNumberPagination 返回格式
# return Response({'code': 100,
# 'msg': '查询成功',
# 'count': pagenation.page.paginator.count,
# 'next': pagenation.get_next_link(),
# 'pre': pagenation.get_previous_link(),
# 'result': ser.data})
# CommonLimitOffsetPagination
# return Response({
# 'code': 100,
# 'msg': '查询成功',
# 'count': pagenation.count,
# 'next': pagenation.get_next_link(),
# 'pre': pagenation.get_previous_link(),
# 'result': ser.data})# CommonCursorPagination
return Response({
'code': 100,
'msg': '查询成功',
'next': pagenation.get_next_link(),
'pre': pagenation.get_previous_link(),
'result': ser.data})
-全局异常:
-写个函数,完成处理,配置到配置文件---》以后只要出了异常,都会走咱么的函数