restful(3):认证、权限、频率 & 解析器、路由控制、分页、渲染器、版本

时间:2023-03-09 02:21:43
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)

认证、权限和频率

 # 认证类中一定要有一个 authenticate() 的方法
# 权限类中一定要有一个 has_permission() 的方法 (认证组件执行时会 request.user = 当前登陆用户)
# 频率类中一定要有一个 allow_request() 的方法 # 执行组件:认证、权限、频率
# 认证:request.user
self.initial(request,*args,**kwargs):
==== # 认证组件
self.perform_authentication(request)
# 权限组件
self.check_permissions(request)
# 频率组件
self.check_throttles(request)
### 这三个组件也是在dispatch()执行的时候执行(有访问请求的时候) 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",] # 写上 认证类的路径
}

认证源码:

restful(3):认证、权限、频率 & 解析器、路由控制、分页、渲染器、版本

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