DRF 十大组件总结:
- 认证
- 权限
- 节流
- 版本
- 解析器
- 序列化
- 分页
- 视图
- 路由
- 渲染器
认证
-
作用:判断用户是否登录,之后登录后的用户才有权限访问接口
-
自定义认证类继承BaseAuthentication
-
认证(判断用户是否登录):
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from drf.models import Token class MyBaseAuthentication(BaseAuthentication): ###自定义认证类 def authenticate(self, request): #在这里完成认证的逻辑 token = request._request.GET.get('token') #token_obj ->Token token_obj = Token.objects.filter(token=token).first() if token_obj: #说明用户是登录的用户 ####### user auth return (token_obj.user,token) else: raise AuthenticationFailed('用户未登录,没有权限访问') #局部使用 class BooksView(APIView): #http://127.0.0.1:8000/api/books/?token=xxxxxxxx # #设置认证类 authentication_classes = [MyBaseAuthentication,] def get(self,request,*args,**kwargs): pass request.user:对应的是认证成功后返回元祖中的第一个参数 request.auth:对应的是认证成功后返回元祖中的第二个参数 #全局使用 #REST_FRAMEWORK:配置Restframework的全局设置 REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES':['tools.authentication.MyBaseAuthentication'] # 设置匿名用户 'UNAUTHENTICATED_USER':lambda :'匿名用户小偷', 'UNAUTHENTICATED_TOKEN':lambda :'123456skcaslbvkasvcvacaj', } #注意:如果设置了全局的认证类,那么想要在某些视图中不进行认证,设置如下: class BooksView(APIView): #http://127.0.0.1:8000/api/books/?token=xxxxxxxx #如果设置了全局的认证,不想让某些视图执行认证 #将authentication_classes设置为空列表 authentication_classes = [] def get(self,request,*args,**kwargs): pass
权限
- 作用:根据用户的身份类型,限定某些接口的访问权限自定义继承自(BasePermission)
class MyBasePermission(BasePermission): #has_permission必须实现,在这里写权限逻辑 message = '用户没有权限访问该接口' def has_permission(self, request, view): #通过这个方法判断用户是否有权限访问接口 if request.user.usertype == 2: #返回True,表示有权限访问接口 return True #返回Flase,表示没有权限访问接口 return False #局部使用(在视图内使用) class BooksView(APIView): ##设置权限类 permission_classes = [MyBasePermission] #全局使用 #REST_FRAMEWORK:配置Restframework的全局设置 REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES':['tools.permissions.MyBasePermission'], } #注意:如果设置了全局的权限判断,在某些视图中如果不需要进行权限判断 class BooksView(APIView): ##设置权限类 permission_classes = []
节流
- 节流作用:节流的作用,限定用户的访问频率(基于ip、基于用户)
#自定义节流类(方式一:BaseThrottle) class MyVisitThrottle(BaseThrottle): def __init__(self): self.history = None def allow_request(self, request, view): #在这里完成节流的逻辑 #获取用户的主机ip(基于ip进行节流) # ip_addr = request.META.get('REMOTE_ADDR') ip_addr = self.get_ident(request) # print(ip_addr) ##基于用户(id或者用户的其他唯一表示) # ip_addr = request.user.id #获取用户访问的时间 ctime = time.time() if ip_addr not in VISIT_HISTORY: #用户是第一次访问,返回True表示允许用户访问 VISIT_HISTORY[ip_addr] = [ctime] return True ##获取用户的访问记录 history = VISIT_HISTORY[ip_addr] self.history = history ##怎么判断用户,是否还有访问次数 ## (是否访问达到了最大频率) #12:04:00 # 12:02:30 12:03:00 while history and history[-1] < ctime-60: history.pop() print(len(history)) if len(history) <= 10: history.insert(0,ctime) return True return False def wait(self): #提示用户需要等待的时间 """ Optionally, return a recommended number of seconds to wait before the next request. """ return 60 - (time.time() - self.history[-1]) #自定义节流类(方式二:SimpleRateThrottle) class MySimpleRateThrottle(SimpleRateThrottle): scope = 'throttle' def get_cache_key(self, request, view): return self.get_ident(request) #return request.user.id #需要在settings文件中添加如下参数 'DEFAULT_THROTTLE_RATES':{ 'throttle':'10/m' } #局部使用 class BooksView(APIView): ###用户每一分钟只能够访问10次 10/60 ###设置节流类,限定用户的访问频率 throttle_classes = [MySimpleRateThrottle] #全局使用 REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES':['tools.throttle.MySimpleRateThrottle'], } #注意:如果设置了全局的限制频率,在某些视图中如果想限制用户的请求频率, class BooksView(APIView): throttle_classes = [] ##源码 #用户未登录每分钟允许访问10次,登录后每分钟允许访问30次 class MySimpleRateThrottle(SimpleRateThrottle): scope = 'unlogin' # scope = 'logined' def get_cache_key(self, request, view): return self.get_ident(request) #return request.user.id def get_rate_with_request(self,request): #自定义get_rate_with_request:根据request,判断用户是否登录 #重置scope属性,获取登录或者未登录的请求频率 self.scope = 'logined' if request.user else 'unlogin' return self.THROTTLE_RATES[self.scope] def allow_request(self, request, view): #在我们判断用户请求是否超出最大访问频率之前, #更新num_requests, duration两个字段 self.rate = self.get_rate_with_request(request) self.num_requests, self.duration = self.parse_rate(self.rate) return super(MySimpleRateThrottle,self).allow_request(request, view) 'DEFAULT_THROTTLE_RATES':{ 'unlogin': '10/m', 'logined': '30/m' }
版本
- 作用:标记url地址
1.强行要求开发在url地址是那个拼接参数
自定义版本(BaseVersioning)from rest_framework.versioning import BaseVersioning,URLPathVersioning #自定义版本类(方法一) class MyVersing(BaseVersioning): def determine_version(self, request, *args, **kwargs): #通过这个方法获取版本号,并返回 # http://127.0.0.1:8000/?version=v1 version = request._request.GET.get('version') return version #局部使用 class BooksView(APIView): ##设置版本类 versioning_class = MyVersing #方式二:使用URLPathVersioning(推荐) #局部使用 class BooksView(APIView): ##设置版本类 versioning_class = URLPathVersioning #注意路由的格式: urlpatterns = [ url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'), url(r'^(?P<version>[v1|v2]+)/users/(?P<pk>[0-9]+)/$', users_detail, name='users-detail') ] ##方式三QueryParameterVersioning class BooksView(APIView): ##设置版本类 versioning_class = QueryParameterVersioning ##全局配置版本 REST_FRAMEWORK = { 'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning', 'DEFAULT_VERSION':'v1', 'ALLOWED_VERSIONS':['v1','v3'], 'VERSION_PARAM':'version' }
解析器
- 作用post请求如果参数以json数据传递,我们要想获取参数,就需要使用到解析器
如果请求头中Content-Type:application/x-www-form-urlencodedname=li&age=23 request._request.POST.get('') #json数据格式 #如果请求头中Content-Type:application/json {"name":"li","age":23} data = json.loads(request.body.decode('utf-8')) name = data.get('name') age = data.get('age') print(name,age) # 使用DRF解析器 #from rest_framework.parsers import FormParser,JSONParser #FormParser:Content-Type:application/x-www-form-urlencoded #JSONParser:Content-Type:application/json #局部使用解析器 class BooksView(APIView): ###设置解析器 parser_classes = [FormParser,JSONParser] def post(self,request,*args,**kwargs): #post请求获取数据 #设置解析器之后取值request.data print(request.data.get('name'),request.data.get('age')) return HttpResponse('post请求') #全局设置 'DEFAULT_PARSER_CLASSES': ( 'rest_framework.parsers.JSONParser', 'rest_framework.parsers.FormParser', 'rest_framework.parsers.MultiPartParser' ), #选择使用哪种解析器 #源码从request.data方法入手, #最终到达如下代码部分 def select_parser(self, request, parsers): """ Given a list of parsers and a media type, return the appropriate parser to handle the incoming request. """ for parser in parsers: if media_type_matches(parser.media_type, request.content_type): return parser return None
序列化
- 1.对queryset的序列化
序列化的作用:将model的序列化为一个字典
方式一:继承自serializers.Serializer(定制性较高,所有的字段都需要自己序列化)class MovieSerializer(serializers.Serializer): """电影列表的序列化""" #使用HyperlinkedIdentityField,反向生成url地址 url = serializers.HyperlinkedIdentityField( view_name='moviedetail', #lookup_field:表中的id lookup_field = 'id', #lookup_url_kwarg:url地址上拼接的参数 lookup_url_kwarg = 'pk' ) name = serializers.CharField() publishtime = serializers.DateTimeField(format='%Y-%m-%d %H:%M') #返回枚举的文本 country = serializers.CharField(source='get_country_display') #获取分类的名称 category = serializers.CharField(source='category.name') category_id = serializers.IntegerField(source='category.id') #自定义序列化方法 category = serializers.SerializerMethodField() def get_category(self,row): #自定义序列化方法 #row:-> 当前序列化的类针对于哪个model的序列化,row就指的是这个model return 要返回的数据 ####方式二继承自serializers.ModelSerializer,是在serializers.Serializer基础上进行了封装,我们使用起来比较简单 class MovieDetailSerializer(serializers.ModelSerializer): """电影详情的序列化""" class Meta: #指定要序列化的model model = Movie #指定要序列化的字段 fields = "__all__" ##序列化所有字段 # fields = ['name','id'] ##序列化部分字段 ####请求数据的验证(post请求) class CategorySerializer(serializers.ModelSerializer): #自定义错误信息 name = serializers.CharField( error_messages={ 'required':'分类名为必填字段' } ) class Meta: model = Category fields = '__all__' #自定义序列化的验证方法 def validate_name(self,value): if value == '动作片': raise ValidationError('该分类数据已存在') return value ####视图中使用序列化验证字段 class CategoryView(APIView): def post(self,request,*args,**kwargs): #添加分类数据 result = { 'code':1 } data = request.data ser = CategorySerializer(data=data) if ser.is_valid(): print('验证通过') print(ser.validated_data) #验证通过,将数据保存至数据库 ser.save() result['msg'] = '创建成功' else: print('数据验证失败',ser.errors) result['code'] = 0 result['msg'] = '创建失败' return Response(result) ######注意:如果序列化的类继承自serializers.Serializer, #在更新或者添加数据的时候,必须实现update()、create()方法 # 相关实现可以在serializers.ModelSerializer寻找 class CategorySerializer(serializers.Serializer): """分类序列化""" name = serializers.CharField(max_length=10) info = serializers.CharField(max_length=256) add_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M') def create(self, validated_data): intance = Category.objects.create(**validated_data) return intance
分页
- 作用:数据量过多的时候,我们需要对数据进行分页
####方式一:继承自PageNumberPagination http://www.baidu.com/?page=1&pagesize=20 from rest_framework.pagination import PageNumberPagination class MyPageNumberPagination(PageNumberPagination): #每页返回的条数( 默认值) page_size = 2 #url地址上表示页码的参数 page_query_param = 'page' #url地址上表示每页返回多少条数据的参数 page_size_query_param = 'pagesize' #每页最多返回多少条数据 max_page_size = 20 ####方式二:继承自LimitOffsetPagination # http://www.baidu.com/?offset=10&limit=10 from rest_framework.pagination import LimitOffsetPagination from collections import OrderedDict class MyLimitOffsetPagination(LimitOffsetPagination): ##每页返回的条数( 默认值) default_limit = 2 #url地址上表示限制返回多少条数据的参数 limit_query_param = 'limit' #url地址上表示偏移量的参数 offset_query_param = 'offset' #每页最多限定返回多少条数据 max_limit = None ####方式三:继承自CursorPagination ###分页方式三(会对url地址上的数据加密) from rest_framework.pagination import CursorPagination class MyCursorPagination(CursorPagination): #url地址上的分页参数 cursor_query_param = 'cursor' ##每页返回的条数( 默认值) page_size = 2 #设置排序字段(-id:表示倒叙,id:表示正序) ordering = 'id' # url地址上表示每页返回多少条数据的参数 page_size_query_param = 'pagesize' #限定每页最多返回多少条数据 max_page_size = 20 ###注意如果需要使用get_paginated_response返回分页的响应结果,并且需要对结果数据进行自定义 ###可以重写get_paginated_response方法 #可以重写get_paginated_response方法,自定义分页返回结果数据 from collections import OrderedDict def get_paginated_response(self, data): return Response(OrderedDict([ ('code',1), ('count', self.count), ('next', self.get_next_link()), ('previous', self.get_previous_link()), ('results', data) ])) ###在视图中的使用方式 class MovieView(APIView): def get(self,request,*args,**kwargs): #get请求获取电影列表 result = { 'code':1 } pk = kwargs.get('pk') if not pk: # pk不存在:获取全部电影列表数据 #从电影表中查询所有数据 queryset = Movie.objects.all() #先获取当前分页数据 #pg = MyPageNumberPagination() # pg = MyLimitOffsetPagination() pg = MyCursorPagination() page_data = pg.paginate_queryset( queryset=queryset, request=request, view=self ) #使用MovieSerializer将queryset序列化 ser = MovieSerializer(instance=page_data,many=True,context={'request': request}) # result['count'] = queryset.count() #返回分页格式的响应结果 return pg.get_paginated_response(ser.data)
视图
-
作用:处理业务逻辑程,实现数据的增删该查
####最原始(FBV): def ShoppingCar(request): if request.method == "GET": pass elif request.method == "POST": pass .... ####最原始:使用的是Django的View视图(CBV) from django.views import View class ShoppingCarView(View): def get(self,request,*args,**kwargs): pass def post(self,request,*args,**kwargs): pass def delete(self,request,*args,**kwargs): pass def put(self,request,*args,**kwargs): pass def patch(self,request,*args,**kwargs): pass ##进化1:使用的是restframework的View视图 class ShoppingCarView(APIView): def get(self,request,*args,**kwargs): pass def post(self,request,*args,**kwargs): pass def delete(self,request,*args,**kwargs): #获取商品的id result = { 'code':1 } try: pk = kwargs.get('pk') # from rest_framework.viewsets import ModelViewSet instance = models.ShopingCar.objects.filter(user=request.user.id,good=pk).first() if instance: #从表中删除数据 instance.delete() result['msg'] = '删除成功' else: result['code'] = 0 result['msg'] = '删除失败' except Exception as err: print(err) result['code'] = 0 result['msg'] = '请求异常' return Response(result) def put(self,request,*args,**kwargs): #自定义更新数据的方法 result = { 'code':1 } data = request.data #先获取需要更新的数据 pk = kwargs.get('pk') instance = models.ShopingCar.objects.filter(id=pk).first() if instance: #默认进行全字段更新 ser = ShopingCarSerializer(instance=instance,data=data) if ser.is_valid(): ser.save() result['msg'] = '数据更新成功' else: result['code'] = 0 print(ser.errors) result['msg'] = '数据更新失败' else: result['code'] = 0 result['msg'] = '数据更新失败,数据不存在' return Response(result) def patch(self,request,*args,**kwargs): #自定义更新数据的方法 result = { 'code':1 } data = request.data #先获取需要更新的数据 pk = kwargs.get('pk') instance = models.ShopingCar.objects.filter(id=pk).first() if instance: #设置partial=True,表示局部更新 ser = ShopingCarSerializer(instance=instance,data=data,partial=True) if ser.is_valid(): ser.save() result['msg'] = '数据更新成功' else: result['code'] = 0 print(ser.errors) result['msg'] = '数据更新失败' else: result['code'] = 0 result['msg'] = '数据更新失败,数据不存在' return Response(result) ##进化2:使用restframe视图中的GenericAPIView, #####只是在ApiView的基础上实现了一些方法和参数,来实现序列化和分页等 from rest_framework.generics import GenericAPIView class ShoppingCarGenericAPIView(GenericAPIView): #获取数据库表中的结果集 queryset = models.ShopingCar.objects.all() #指定要序列化的类 serializer_class = ShopingListCarSerializer #添加分页,指定分页的类 pagination_class = MyPageNumberPagination def get(self,request,*args,**kwargs): car_goods = self.get_queryset() #获取当前分页的数据 page_data = self.paginate_queryset(car_goods) #=> pg=MyPageNumberPagination() pg..paginate_queryset(car_goods) #序列化分页数据 ser = self.get_serializer(instance=page_data,many=True) #=>ShopingListCarSerializer(instance=car_goods,many=True) print(ser.data) ###获取如果做分页了,返回分页的结果集 return self.get_paginated_response(ser.data) # return Response(ser.data) ####进化3:(注意如果使用如下视图,路由发生了该改变) #使用GenericViewSet和ListModelMixin,UpdateModelMixin, #CreateModelMixin,DestroyModelMixin,RetrieveModelMixin ##GenericViewSet:继承自ViewSetMixin, generics.GenericAPIView,一般情况下 ##跟ListModelMixin,UpdateModelMixin,CreateModelMixin,DestroyModelMixin, #GenericViewSet # RetrieveModelMixin配合使用 # ListModelMixin:get请求,获取列表数据 # UpdateModelMixin:put请求,更新数据 # CreateModelMixin:post请求,创建数据 # DestroyModelMixin:delete请求,删除数据 # RetrieveModelMixin:get请求,获取详情数据 class ShoppingCarGenericAPIView(ListModelMixin,CreateModelMixin, DestroyModelMixin,UpdateModelMixin, RetrieveModelMixin,GenericViewSet): # 获取数据库表中的结果集 queryset = models.ShopingCar.objects.all() # 指定要序列化的类 serializer_class = ShopingListCarSerializer # 添加分页,指定分页的类 pagination_class = MyPageNumberPagination #动态设置序列化的类 def get_serializer_class(self): if self.action == 'list': return ShopingListCarSerializer elif self.action == 'retrieve': #获取详情数据 return ShopingCarDetailSerializer return ShopingCarSerializer def retrieve(self, request, *args, **kwargs): result = {} instance = self.get_object() serializer = self.get_serializer(instance) result['code'] = 1 result['data'] = serializer.data return Response(result) #### 注意路由的变化: url(r'^api/(?P<version>[v1|v2]+)/shopcar/$', ShoppingCarGenericAPIView.as_view({'get': 'list','post':'create'})), #destroy url(r'^api/(?P<version>[v1|v2]+)/shopcar/(?P<pk>\d+)/$', ShoppingCarGenericAPIView.as_view({"get":"retrieve",'delete': 'destroy',"put":"update",'patch':'partial_update'})), ####总结: 1.如果视图部分的逻辑比较复杂,ListModelMixin...都满足不了的时候,建议使用APiView 写视图类 2.如果只是要实现一些简单的增删改查功能,也可以重写xxxxModelMixin中的方法实现自己的视图逻辑 # GenericViewSet和一下类配合使用 # RetrieveModelMixin配合使用 # ListModelMixin:get请求,获取列表数据 # UpdateModelMixin:put请求,更新数据 # CreateModelMixin:post请求,创建数据 # DestroyModelMixin:delete请求,删除数据 # RetrieveModelMixin:get请求,获取详情数据 3.如果要实现全部的增删改查可以直接继承自ModelViewSet
路由
-
自动路由匹配使用如下
from rest_framework.routers import DefaultRouter ##实例化自动路由对象 router = DefaultRouter() router.register(r'shopcar',ShoppingCarGenericAPIView,base_name='shopcar') urlpatterns = [ url(r'^api/(?P<version>[v1|v2]+)/',include(router.urls)) ] ##会帮我们生成如下url地址 #^api/(?P<version>[v1|v2]+)/ ^shopcar/$ [name='shopcar-list'] #^api/(?P<version>[v1|v2]+)/ ^shopcar\.(?P<format>[a-z0-9]+)/?$ [name='shopcar-list'] #^api/(?P<version>[v1|v2]+)/ ^shopcar/(?P<pk>[^/.]+)/$ [name='shopcar-detail'] #^api/(?P<version>[v1|v2]+)/ ^shopcar/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='shopcar-detail']
渲染器
-
返回数据,展示的模版
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer,AdminRenderer ##使用: class ShoppingCarGenericAPIView(ModelViewSet): ##设置渲染器 renderer_classes = [JSONRenderer,AdminRenderer]