restful(3):认证、权限、频率 & 解析器、路由控制、分页、渲染器、版本
models.py中:
class UserInfo(models.Model): name = models.CharField(max_length=32) psw = models.CharField(max_length=32) user_type_choices = ((1,"普通"),(2,"VIP"),(3,"SVIP")) user_type = models.SmallIntegerField(choices=user_type_choices,default=1) # 新添加一个标识用户权限级别的字段 class Token(models.Model): # Token类用于 认证 user = models.OneToOneField(to="UserInfo",on_delete=models.CASCADE) token = models.CharField(max_length=128)
认证、权限和频率
1 # 认证类中一定要有一个 authenticate() 的方法 2 # 权限类中一定要有一个 has_permission() 的方法 (认证组件执行时会 request.user = 当前登陆用户) 3 # 频率类中一定要有一个 allow_request() 的方法 4 5 6 # 执行组件:认证、权限、频率 7 # 认证:request.user 8 self.initial(request,*args,**kwargs): 9 ==== # 认证组件 10 self.perform_authentication(request) 11 # 权限组件 12 self.check_permissions(request) 13 # 频率组件 14 self.check_throttles(request) 15 ### 这三个组件也是在dispatch()执行的时候执行(有访问请求的时候) 16 17 request.META.get("REMOTE_ADDR") # 客户端的IP地址
认证组件:
局部视图认证:
在app01.service.auth.py:
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed class Authentication(BaseAuthentication): def authenticate(self,request): # authenticate() 这个方法名是固定的 # http:www...../?token=soihtfn7a9sdfvb987... # url中应该带有token # token=request._request.GET.get("token") token = request.query_params.get("token") # request.query_params # 获取到 GET请求数据(/? 后面的数据,和请求方式无关;POST请求时,也能 request.GET来获取 /? 后面的数据);request是后来封装好的request token_obj=UserToken.objects.filter(token=token).first() if not token_obj: # 检查token是否存在 raise AuthenticationFailed("验证失败!") # 认证失败时的固定语法 return (token_obj.user,token_obj) # 认证成功后需要返回一个元组:第一个是用户有关的信息,第二个参数是token对象
在views.py:
def get_random_str(user): import hashlib,time ctime=str(time.time()) md5=hashlib.md5(bytes(user,encoding="utf8")) # user是为了“加盐”处理 md5.update(bytes(ctime,encoding="utf8")) return md5.hexdigest() from app01.service.auth import * from django.http import JsonResponse class LoginViewSet(APIView): # authentication_classes = [Authentication,] # authentication_classes 是固定写法;需要认证的类都写在后面的列表中 def post(self,request,*args,**kwargs): res={"code":1000,"msg":None} try: user=request._request.POST.get("user") pwd=request._request.POST.get("pwd") user_obj=UserInfo.objects.filter(user=user,pwd=pwd).first() print(user,pwd,user_obj) if not user_obj: res["code"]=1001 res["msg"]="用户名或者密码错误" else: token=get_random_str(user) UserToken.objects.update_or_create(user=user_obj,defaults={"token":token}) # 表中没有就创建,有就更新;# defaults表示:除了defaults 中的字段外,其它的字段联合比较是否已经存在,存在则更新,不存在则创建 # 返回一个元组:第一个是对象,第二个是布尔值(表示create还是update) res["token"]=token except Exception as e: res["code"]=1002 res["msg"]=e return JsonResponse(res,json_dumps_params={"ensure_ascii":False}) # {"ensure_ascii":False} 作用:显示中文
全局视图认证组件:
settings.py配置如下:
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",] # 写上 认证类的路径 }
认证源码:
self表示封装之后的request,所以,认证完成之后,request.user 和 request.auth 就是你赋给它们的值
自定义用户认证的类:
# 自定义用户验证的类(如手机或邮箱配合密码登陆;因为默认是 用户名和密码验证)需要继承 ModelBackend;并且需要在 settings 中设置 AUTHENTICATION_BACKENDS = ("路径.自定义用户验证的类",) from django.contrib.auth.backends import ModelBackend from django.db.models import Q class CustomBackend(ModelBackend): def authenticate(self,username=None,password=None,**kwargs): try: user = models.User.objects.get(Q(username=username)|Q(email=username)|Q(mobile=username)) if user.check_password(password): # 前端传过来的密码是明文的,Django中保存的是密文,check_password() 会把明文转化为密文 return user except Exception as e: return None
权限组件
局部视图权限
在app01.service.permissions.py中:
from rest_framework.permissions import BasePermission class SVIPPermission(BasePermission): message="SVIP才能访问!" # 没有权限时返回的错误信息 def has_permission(self, request, view): if request.user.user_type==3: return True return False # return True就是通过权限认证, return False 即没有权限
在views.py:
from app01.service.permissions import * class BookViewSet(generics.ListCreateAPIView): permission_classes = [SVIPPermission,] # permission_classes 是固定写法;需要校验的权限类都写在后面的列表中(这是局部权限校验) queryset = Book.objects.all() serializer_class = BookSerializers
全局视图权限:
settings.py配置如下:
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",], "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",] # 写上权限认证类的路径 }
登陆权限的类:IsAuthenticated
from rest_framework.permissions import IsAuthenticated class xxxViewSet(): permission_classes = (IsAuthenticated,) # 该视图只有登陆后才能访问
只有对象的拥有者才能有权限操作(如:只能删除自己的收藏):
# 只有对象的拥有者才能有权限操作(如:只能删除自己的收藏) class IsOwnerOrReadOnly(permissions.BasePermission): """ Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. """ def has_object_permission(self, request, view, obj): # obj 表示被操作的对象,如一条收藏记录 # Read permissions are allowed to any request, # so we'll always allow GET, HEAD or OPTIONS requests. if request.method in permissions.SAFE_METHODS: return True # Instance must have an attribute named `owner`. return obj.owner == request.user
只能查看自己的内容(GET请求;)
class UserFavViewSet(mixin.CreateModelMixin,mixin.ListModelMixin,mixin.RetrieveMixin,generics.GenericAPIView): authentication_classes = (JSONWebTokenAuthentication,SessionAuthentication) # ?? 此处有疑问 permission_classes = (IsAuthenticated,IsOwnerReadOnly) serializer_class = UserFavSerializer lookup_field = "goods_id" # 用于执行各个model实例的对象查找的model字段,默认是 "pk"; 程序先 执行的 get_queryset() ,然后才走的 这一步 def get_queryset(self): # 有了 get_queryset() 这个方法时, 上面就不再需要写 queryset return UserFav.objects.filter(user=self.request.user) # 筛选出 user 为当前登陆用户的 记录 (即 只能查看自己的)
throttle(访问频率)组件
局部视图throttle
在app01.service.throttles.py中:
from rest_framework.throttling import BaseThrottle VISIT_RECORD={} class VisitThrottle(BaseThrottle): def __init__(self): self.history=None def allow_request(self,request,view): # allow_request()是固定的方法名 # 以下为业务逻辑(rest 只处理数据,不处理逻辑) remote_addr = request.META.get('REMOTE_ADDR') # 客户端的IP地址 print(remote_addr) import time ctime=time.time() if remote_addr not in VISIT_RECORD: VISIT_RECORD[remote_addr]=[ctime,] return True history=VISIT_RECORD.get(remote_addr) self.history=history while history and history[-1]<ctime-60: history.pop() if len(history)<3: history.insert(0,ctime) return True # return True 表示通过验证 else: return False # return False 表示没通过验证 def wait(self): import time ctime=time.time() return 60-(ctime-self.history[-1])
在views.py中:
from app01.service.throttles import * class BookViewSet(generics.ListCreateAPIView): throttle_classes = [VisitThrottle,] # throttle_classes 是固定写法; queryset = Book.objects.all() serializer_class = BookSerializers
全局视图throttle
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",], "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",], "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",] }
内置throttle类
在app01.service.throttles.py修改为:
class VisitThrottle(SimpleRateThrottle): scope="visit_rate" def get_cache_key(self, request, view): return self.get_ident(request)
settings.py设置:
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",], "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",], "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",], "DEFAULT_THROTTLE_RATES":{ "visit_rate":"5/m", } }
使用默认的Throttling:
1. 配置settings
REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': ( 'rest_framework.throttling.AnonRateThrottle', 'rest_framework.throttling.UserRateThrottle' ), 'DEFAULT_THROTTLE_RATES': { 'anon': '100/day', 'user': '1000/day' } }
2. 在相关视图中添加 throttle_classes 的类,如:
from rest_framework.throttling import AnonRateThrottle,UserRateThrottle class GoodsViewSet(mixins.ListModelMixin,mixins.RetrieveModelMixin,GenericViewSet): """ 商品列表页,分页,过滤,搜索,排序 list: 所有商品列表 retrieve: 查看单个商品 """ queryset = Goods.objects.all().order_by("pk") serializer_class = GoodsSerializer pagination_class = GoodsPagination throttle_classes = (AnonRateThrottle,UserRateThrottle) # 控制频率的类 filter_backends = (DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter) filter_class = GoodsFilter # 过滤 search_fields = ("name","goods_brief","goods_details") # 搜索 ordering_fields = ("sold_num","shop_price") # 排序 # 修改点击数 def retrieve(self, request, *args, **kwargs): instance = self.get_object() # instance 是一个 Goods() 的对象 instance.click_num += 1 # 点击数 +1 instance.save() serializer = self.get_serializer(instance) return Response(serializer.data)
解析器
局部视图
from rest_framework.parsers import JSONParser,FormParser class PublishViewSet(generics.ListCreateAPIView): parser_classes = [FormParser,JSONParser] # parser_classes 是固定写法;解析器名放在后面的列表中 queryset = Publish.objects.all() serializer_class = PublshSerializers def post(self, request, *args, **kwargs): print("request.data",request.data) return self.create(request, *args, **kwargs)
全局视图
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",], "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",], "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",], "DEFAULT_THROTTLE_RATES":{ "visit_rate":"5/m", }, "DEFAULT_PARSER_CLASSES":['rest_framework.parsers.FormParser',] }
路由控制
路由控制针对的只是以下这种情况:
# urls.py部分: re_path(r"^books/$",views.BookModelView.as_view({"get":"list","post":"create"})), re_path(r"^books/(?P<pk>\d+)/$",views.BookModelView.as_view({"get":"retrieve","put":"update","delete":"destroy"})), # views.py部分: class BookModelView(viewsets.ModelViewSet): queryset = models.Book.objects.all() # queryset 表示要处理的数据;queryset这个变量名是固定的 serializer_class = serializer.BookSerializers # serializer_class 表示 所要用到的 序列化的类;serializer_class 是固定写法
上面的两条 url 可以利用 路由控制 组件来简化:
# urls.py 中 from rest_framework import routers from django.urls import path,re_path,include from app01 import views routers = routers.DefaultRouter() routers.register("books",views.BookModelView) # 第一个参数是路径的前缀,第二参数是 视图类 名称 # 这两行代码执行后,会生成四条 以 books/ 为前缀的 url urlpatterns = [ re_path(r'^', include(routers.urls)), # 把上面 register() 的路径在 urlpatterns 中 include 一下 ]
分页
普通分页
from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination class PNPagination(PageNumberPagination): page_size = 1 # 后端设置的每页条数 page_query_param = 'page' # 前端查询页码的参数 page_size_query_param = "size" # 前端临时修改每页条数的参数 max_page_size = 5 # 前端能修改的每页条数 的最大值 class BookViewSet(viewsets.ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializers # 继承 ModelViewSet 时 需要修改其 list() 方法 def list(self,request,*args,**kwargs): book_list=Book.objects.all() pp=PNPagination() pager_books=pp.paginate_queryset(queryset=book_list,request=request,view=self) # 分页函数 print(pager_books) bs=BookSerializers(pager_books,many=True) return Response(bs.data) # return pp.get_paginated_response(bs.data)
偏移分页
from rest_framework.pagination import LimitOffsetPagination
响应器:
from rest_framework.response import Response
# Response 内部会自动做序列化
渲染器:
渲染器作用:规定页面显示的效果(无用)
局部渲染:
from rest_framework.renderers import JSONRenderer class TestView(APIView): renderer_classes = [JSONRenderer, ] # renderer_classes 渲染器固定写法; 通常用 都用 JSONRenderer--- 只渲染为 Json字符串 def get(self, request, *args, **kwargs): user_list = models.UserInfo.objects.all() ser = TestSerializer(instance=user_list, many=True) return Response(ser.data)
全局渲染配置:
REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES':['rest_framework.renderers.JSONRenderer',] }
版本
a. 基于url的get传参方式: 如:/users?version=v1
settings 中的配置:
REST_FRAMEWORK = { 'DEFAULT_VERSION': 'v1', # 默认版本 'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本 'VERSION_PARAM': 'version' # URL中获取值的key }
from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'^test/', TestView.as_view(),name='test'), ] urls.py
#!/usr/bin/env python # -*- coding:utf-8 -*- from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.versioning import QueryParameterVersioning class TestView(APIView): versioning_class = QueryParameterVersioning def get(self, request, *args, **kwargs): # 获取版本 print(request.version) # 获取版本管理的类 print(request.versioning_scheme) # 反向生成URL reverse_url = request.versioning_scheme.reverse('test', request=request) print(reverse_url) return Response('GET请求,响应内容') def post(self, request, *args, **kwargs): return Response('POST请求,响应内容') def put(self, request, *args, **kwargs): return Response('PUT请求,响应内容') views.py
b. 基于url的正则方式:
如:/v1/users/
REST_FRAMEWORK = { 'DEFAULT_VERSION': 'v1', # 默认版本 'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本 'VERSION_PARAM': 'version' # URL中获取值的key }
from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'^(?P<version>[v1|v2]+)/test/', TestView.as_view(), name='test'), ] urls.py
#!/usr/bin/env python # -*- coding:utf-8 -*- from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.versioning import URLPathVersioning class TestView(APIView): versioning_class = URLPathVersioning def get(self, request, *args, **kwargs): # 获取版本: request.version print(request.version) # 获取版本管理的类 print(request.versioning_scheme) # 反向生成URL reverse_url = request.versioning_scheme.reverse('test', request=request) print(reverse_url) return Response('GET请求,响应内容') def post(self, request, *args, **kwargs): return Response('POST请求,响应内容') def put(self, request, *args, **kwargs): return Response('PUT请求,响应内容') # views.py
全局使用:
REST_FRAMEWORK = { 'DEFAULT_VERSIONING_CLASS':"rest_framework.versioning.URLPathVersioning", 'DEFAULT_VERSION': 'v1', 'ALLOWED_VERSIONS': ['v1', 'v2'], 'VERSION_PARAM': 'version' } # settings.py
Code your future.