认证,权限,频率,过滤-排序,分页,全局异常
Ⅰ 认证
# 三大认证依次执行
-认证类
-权限类
-频率类
# APIView---->dispatch---> self.initial(request, *args, **kwargs)-->
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
【一】数据准备
# 1 登录认证
# 2 作用:某个接口,必须登录后才能访问【认证过后才能访问】
# 3 前戏:写个登录接口-->用户表
【二】用户注册
- models.py
from django.db import models
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
age = models.IntegerField()
- serializer.py
from .models import User
from rest_framework import serializers
# 用户注册
class UserSerialzier(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username', 'password','age']
- views.py
from rest_framework.viewsets import ModelViewSet
from .models import User,UserToken
from .serializer import UserSerialzier
# 用户注册
class UserView(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerialzier
- urls.py
from django.urls import path
from .views import UserView
from .views import UserViewSet
urlpatterns = [
# 用户注册
path('users/', UserView.as_view({'get': 'list', 'post': 'create'})),
path('users/<int:pk>/', UserView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
]
【二】登录接口
- models.py
from django.db import models
import uuid
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
age = models.IntegerField()
class UserToken(models.Model):
token = models.CharField(max_length=255) # 记录用户登录信息
user = models.OneToOneField(to=User, on_delete=models.CASCADE)
# 一对一的表 习惯上把外键关系放在用的多的一方
# 我们是回头还有用token来验证登录,我们习惯是正着.来查询 所以回头获取数据就是token.user得到User里面的数据
- urls.py
from django.urls import path
from .views import UserView
from .views import UserViewSet
# 自动生成路由
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('user', UserViewSet,'user')
urlpatterns = [
# 用户注册
path('users/', UserView.as_view({'get': 'list', 'post': 'create'})), # get
path('users/<int:pk>/', UserView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})), # get
]
urlpatterns += router.urls
- serializer.py
from .models import User
from rest_framework import serializers
# 用户注册
class UserSerialzier(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username', 'password','age']
- views.py
from rest_framework.viewsets import ModelViewSet
from .models import User,UserToken
from .serializer import UserSerialzier
# 用户注册
class UserView(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerialzier
# 写登录接口 ,如果是 GenericViewSet 你就得写queryset,serializer_class两句话 只是登录,所以用这个不合适
from rest_framework.viewsets import ViewSet, GenericViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
import uuid
# 写登录接口
class UserViewSet(ViewSet):
@action(methods=['POST'], detail=False) # api/v1/user/login/-->post 才会触发这段代码
def login(self, request):
# 取出前端传入的用户名,密码
username = request.data.get('username')
password = request.data.get('password')
# 校验用户名和密码是否正确
# 这里不再作具体的用户密码校验,防止有些人为了证明是否注册过,或者是拿注册之后一次次验证密码
user = User.objects.filter(username=username, password=password).first()
if user:
# 校验成功,签发token,生成一个随机字符串,保存到数据库中,返回给前端
# 暂时先用uuid生成随机不重复的字符串来代替token
token = str(uuid.uuid4())
# 保存到数据库,如果数据库原来有了就替换,没有就新增
UserToken.objects.update_or_create(defaults={'token': token}, user=user)
return Response({'code': 200, 'msg': '登陆成功', 'token': token})
else:
return Response({'code': 101, 'msg': '账号或密码错误'})
【三】几个book的接口
- models.py
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.IntegerField()
- serializer.py
from .models import User,Book
from rest_framework import serializers
# 图书
class BookSerialzier(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
- views.py
from rest_framework.mixins import (ListModelMixin, RetrieveModelMixin,
UpdateModelMixin,DestroyModelMixin,CreateModelMixin)
from rest_framework.viewsets import ModelViewSet, GenericViewSet
from .models import Book
from .serializer import UserSerialzier,BookSerialzier
# book
class BookView(GenericViewSet,ListModelMixin, RetrieveModelMixin, UpdateModelMixin,DestroyModelMixin,CreateModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerialzier
- urls.py
from django.urls import path
from .views import BookView
# 自动生成路由
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('books', BookView,'books')
urlpatterns = [
]
urlpatterns += router.urls
【四】编写认证类
# 【1】 写一个类,继承BaseAuthentication
# 【2】 在类中,重写authenticate
# 【3】 在方法中完成,登录信息的认证
- 要求用户把 登录信息,放在请求头中, 叫token
- 取:request.META.get('HTTP_TOKEN')
# 【4】 如果认证通过,返回两个值-->后续在视图类的方法中-->request.user 拿到的就是 第一个参数返回的值
return user_token.user,token
# 【5】 认证失败,抛异常
# 【6】 把认证类配置在视图类上-->视图类就会受认证类的控制
authentication_classes = [LoginAuth]
# 【7】 全局配置:配置文件
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'app01.auth.LoginAuth',
),
}
# 【8】 局部禁用-->登录要局部禁用
authentication_classes = []
- auth.py
from rest_framework.authentication import BaseAuthentication
from .models import UserToken
from rest_framework.exceptions import AuthenticationFailed
class LoginAuth(BaseAuthentication):
def authenticate(self, request):
# get请求没有请求体 要求用户把登录信息带在 请求头
# 取出token
token = request.META.get('HTTP_TOKEN')
# 校验用户
user_token = UserToken.objects.filter(token=token).first()
if user_token:
# 校验通过
return user_token.user,token
else:
raise AuthenticationFailed("您没有登录,请先登录!!!")
- 前端带进来的token
- 查看认证的源码
- 在settings里面进行全局认证
【1】在settings中,导入一些不用的模块,可能会导致系统启动不了
# 在settings中,导入一些不用的模块,可能会导致系统启动不了
from app01.auth import LoginAuth
Ⅱ 权限
# 当前用户登录了,但是系统分用户类型,不同用户权限不一样
通过权限类来限制
# 用户登录,并且是超级用户才能 对图书的接口进行操作
【一】数据准备
- 给用户模型表,增加一个用户类型字段
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
age = models.IntegerField()
user_type = models.IntegerField(choices=((1,"普通用户"),(2,"vip用户"),(3,"超级管理员")),default=1)
- 多个选择的数据,不给默认值 ,迁移数据就会出现以下错误
【二】使用步骤
# 【1】 写一个类,继承BasePermission
# 【2】 在类中,重写has_permission
# 【3】 在方法中完成,权限的校验
# 【4】 如果有权限,返回True
# 【5】 如果没有权限,返回False,通过 self.message 修改 错误提示
# 【6】 把权限类配置在视图类上--》视图类就会受权限类的控制
-局部配置: permission_classes = [BasePerm]
# 【7】 全局配置:配置文件
'DEFAULT_PERMISSION_CLASSES': [
'app01.permession.BasePerm',
],
# 【8】 局部禁用-->登录要局部禁用
permission_classes = []
- 代码
# 权限
from rest_framework.permissions import BasePermission
class BasePerm(BasePermission):
def has_permission(self, request, view):
# view.request = request
# view.action
# 判断用户是否有权限
# 取出用户类型
user_type = request.user.user_type
# 对类型进行判断
# 如果有权限,返回True
if user_type == 3: # 字符串的3 和 数字3 比较是不相等
return True
# 如果没权限,返回False
else:
self.message=r'您的身份是【%s】,不是【超级管理员】,不能访问!!!'%request.user.get_user_type_display()
return False
Ⅲ 频率
【一】介绍
# 限制用户的访问频次
- 限制条件是唯一的:ip地址,用户id,设备id号
# 以ip地址,限制,同一个ip地址,一分钟只能请求三次
【二】使用步骤
# 【1】 写一个类,继承SimpleRateThrottle
# 【2】 在类中,重写get_cache_key
# 【3】 方法返回什么,就以什么做频率限制
# 【4】 把频率类配置在视图类上-->视图类就会受频率类的控制
-局部配置: throttle_classes = [CommonThrottle]
# 【5】 全局配置:配置文件
'DEFAULT_THROTTLE_CLASSES': (
'app01.throttles.CommonThrottle',
),
# 【6】 局部禁用--》登录要局部禁用
throttle_classes = []
- throttles.py
from rest_framework.throttling import SimpleRateThrottle,BaseThrottle
# SimpleRateThrottle继承了BaseThrottle
class CommonThrottle(SimpleRateThrottle):
# 访问限制
rate = "3/m" # 有s,m,h,d 秒,分,时,天
def get_cache_key(self, request, view):
# 返回什么,就以什么做限制
ip = request.META.get('REMOTE_ADDR')
# 如果是用户id或者是手机号
# user_id = request.user.pk
# phone_id = request.META.get('REMOTE_PHONE_ID')
return ip
Ⅳ 过滤-排序
# 过滤和排序只针对于查询所有
【一】排序
使用方式
# 使用方式,在继承GenericAPIView的视图类上配置
from rest_framework.filters import OrderingFilter
class BookView(GenericViewSet,ListModelMixin, RetrieveModelMixin, UpdateModelMixin,DestroyModelMixin,CreateModelMixin):
# authentication_classes = [LoginAuth]
permission_classes = [BasePerm]
throttle_classes = [CommonThrottle]
# 排序 filter_backends 只有继承GenericAPIView才能使用 apiview只能自己写,排序:orderby取出过滤条件,再序列化返回前端
# http://localhost:8000/api/v1/books/?ordering=price
# http://localhost:8000/api/v1/books/?ordering=-price # 降序
# http://localhost:8000/api/v1/books/?ordering=price,id
filter_backends = [OrderingFilter] #GenericAPIView 的属性 filter_backends,用来控制 过滤和排序的
ordering_fields = ['price','id'] # 按价格排序
【二】过滤
【1】drf内置的,只能用search模糊匹配
# 模糊查询
from rest_framework.filters import SearchFilter
from .throttles import CommonThrottle
from .auth import LoginAuth
class BookView(GenericViewSet,ListModelMixin, RetrieveModelMixin, UpdateModelMixin,DestroyModelMixin,CreateModelMixin):
# authentication_classes = [LoginAuth]
permission_classes = [BasePerm]
throttle_classes = [CommonThrottle]
# 排序 filter_backends 只有继承GenericAPIView才能使用 apiview只能自己写,排序:orderby取出过滤条件,再序列化返回前端
filter_backends = [OrderingFilter,SearchFilter] #GenericAPIView 的属性 filter_backends,用来控制 过滤和排序的
ordering_fields = ['price','id'] # 按价格排序
# 模糊查询 http://localhost:8000/api/v1/books/?ordering=price,id&search=龙
search_fields = ['name'] # ['name','price'] 名字和price里面含有指定的
queryset = Book.objects.all()
serializer_class = BookSerialzier
【2】第三方 django-filter
# 安装 pip install django_filter
# 精准查询
from django_filters.rest_framework.backends import DjangoFilterBackend
from .throttles import CommonThrottle
from .auth import LoginAuth
class BookView(GenericViewSet,ListModelMixin, RetrieveModelMixin, UpdateModelMixin,DestroyModelMixin,CreateModelMixin):
# authentication_classes = [LoginAuth]
permission_classes = [BasePerm]
throttle_classes = [CommonThrottle]
# 排序 filter_backends 只有继承GenericAPIView才能使用 apiview只能自己写,排序:orderby取出过滤条件,再序列化返回前端
filter_backends = [OrderingFilter,DjangoFilterBackend] #GenericAPIView 的属性 filter_backends,用来控制 过滤和排序的
ordering_fields = ['price','id'] # 按价格排序
# search_fields = ['name']
# http://localhost:8000/api/v1/books/?ordering=price,id&name=红楼梦&price=100
filterset_fields = ['name','price'] # ['name','price'] 这里是and 两个都满足才输出出来
queryset = Book.objects.all()
serializer_class = BookSerialzier
【3】自定义过滤类
- filters.py
from rest_framework.filters import BaseFilterBackend
from django.db.models import Q
class BaseFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
name = request.query_params.get('name')
price = request.query_params.get('price')
if name and price:
queryset = queryset.filter(Q(name__contains=name) | Q(price=price)) # __contains可以进行模糊查询
elif name:
queryset = queryset.filter(name__contains=name)
elif price:
queryset = queryset.filter(price=price)
return queryset
# 自定义查询
from .filters import BaseFilter
from .throttles import CommonThrottle
from .auth import LoginAuth
class BookView(GenericViewSet,ListModelMixin, RetrieveModelMixin, UpdateModelMixin,DestroyModelMixin,CreateModelMixin):
# authentication_classes = [LoginAuth]
permission_classes = [BasePerm]
throttle_classes = [CommonThrottle]
# 排序 filter_backends 只有继承GenericAPIView才能使用 apiview只能自己写,排序:orderby取出过滤条件,再序列化返回前端
filter_backends = [OrderingFilter,BaseFilter] #GenericAPIView 的属性 filter_backends,用来控制 过滤和排序的
ordering_fields = ['price','id'] # 按价格排序
# search_fields = ['name']
# http://localhost:8000/api/v1/books/?name=红&price=100
filterset_fields = ['name','price'] # name=红&price=100虽然写的是&查询 实际输出出来是或查询
- name=红&price=100虽然写的是&查询 实际输出出来是或查询
Ⅴ 分页
# 分页只针对于查询所有
-如果是app--->下拉加载下一页
-web端--->点击下一页
# drf提供了三种分页方式
# 使用步骤:
1 定义一个类,继承三个分页类之一
2 在分页类中,重写某些类属性
3 在继承GenericAPIView的子类中,配置
pagination_class = CommonCursorPagination
【一】基本分页PageNumberPagination
【1】代码实现
from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination,CursorPagination
# PageNumberPagination 基本分页,使用最多
class CommonPageNumberPagination(PageNumberPagination):
# http://api.example.org/accounts/?page=4
# http://api.example.org/accounts/?page=4&size=100
page_size = 2 # 默认一页显示两条
page_query_param = 'page' # 查询条件 page=4
page_size_query_param = "size" # 每页显示多少条的查询条件 size=100
max_page_size = 5 # 虽然可以指定某页显示多少条,但是最多显示5条
【2】使用
# 分页
from .paging import CommonPageNumberPagination
class BookView(GenericViewSet,ListModelMixin, RetrieveModelMixin, UpdateModelMixin,DestroyModelMixin,CreateModelMixin):
# 分页
# http://localhost:8000/api/v1/books/?page=1&size=10
pagination_class = CommonPageNumberPagination
- 总览
- 下一页
- 添加每页展览数量
【二】偏移分页LimitOffsetPagination
【1】代码实现
from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination,CursorPagination
# LimitOffsetPagination 偏移分页
class CommonLimitOffsetPagination(LimitOffsetPagination):
# http://api.example.org/accounts/?limit=100
# http://api.example.org/accounts/?offset=400&limit=100
default_limit = 2 # 默认条数
limit_query_param = 'limit' # 每页显示多少条的查询条件 limit=100
offset_query_param = 'offset' # 偏移量--> 从第一条开始,偏移的位置
max_limit = 5 # 最大显示条数
【2】使用
from .paging import CommonLimitOffsetPagination
class BookView(GenericViewSet,ListModelMixin, RetrieveModelMixin, UpdateModelMixin,DestroyModelMixin,CreateModelMixin):
# 偏移分页
# http://localhost:8000/api/v1/books/?offset=2&limit=3
pagination_class = CommonLimitOffsetPagination
- 不加条件展览
- 偏移到第几条,拿几条数据
【三】游标分页CursorPagination
【1】代码实现
from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination,CursorPagination
class CommonCursorPagination(CursorPagination):
page_size = 2 # 每页显示条数
cursor_query_param = 'cursor' # 查询条件
ordering = 'id' # 按某个字段排序--> 这个字段必须是表中的字段
【2】使用
from .paging import CommonLimitOffsetPagination
class BookView(GenericViewSet,ListModelMixin, RetrieveModelMixin, UpdateModelMixin,DestroyModelMixin,CreateModelMixin):
# 游标分页:只能上一页,下一页跳转-->但是效率高,大数据量分页快
# 因为其余两个还要读取前面的很多数据 所以效率低
# 必须根据上一页的返回,才知道下一页地址是什么
pagination_class = CommonCursorPagination
Ⅵ 全局异常
【一】继承APIView及子类的视图类,执行的异常函数
-在执行:三大认证
-在执行:过滤
-在执行:分页
-在执行:视图类的方法
过程中,只要出了异常,都会被异常捕获,统一执行一个【函数】exception_handler
【二】异常的3大类
# 【1】 drf的异常:APIException,ValidationError,AuthenticationFailed-->默认exception_handler会处理,前端捕获抛错了
# 【2】 django的异常:exception_handler不会处理,前端会抛出错误
# 【3】 python程序的异常:主动抛raise,除以0,列表越界,exception_handler不会处理,前端会抛出错误
【三】个人写的异常处理函数注意点
# 我们需要自己写一个异常处理函数,统一处理所有的异常,一定要放到配置文件中
-有在再出了异常,就会走咱们自己的 函数
【四】自定义异常处理函数目的
# 重点:我们自定义异常函数的目的
1 统一返回格式
2 日志记录
【五】使用步骤
# 使用步骤
1 写个方法
2 配置在配置文件中
REST_FRAMEWORK={
'EXCEPTION_HANDLER': 'app01.exception.common_exception_handler',
}
【六】代码实现
from rest_framework.views import exception_handler
import time
from rest_framework.response import Response
def common_exception_handler(exc,context):
# print(exc) # 异常对象
# print(context) # 包含view , request
# drf的异常,会返回Response的对象
# 其他异常,返回None
# response :Response的对象 或 None
request = context.get('request')
view = context.get('view')
# 只要执行这个函数,就是出错了,我们就记日志
# 越详细越好
print(
f'时间:{time.time()},用户:{request.user.username or "匿名用户"},在访问地址:{request.get_full_path()},请求方式是:{request.method},视图类是:{str(view)}')
# 统一返回格式
response = exception_handler(exc, context) # 执行原来的--->处理drf的异常
if response: # 有值,说明是drf的异常
if isinstance(response.data, dict):
msg = response.data.get('detail') or '未知错误,请联系系统管理员'
elif isinstance(response.data, list):
msg = response.data[0]
return Response({'code': 999, 'msg': f'drf异常,异常信息是:{msg}'})
else:
return Response({'code': 888, 'msg': f'程序异常,异常信息是:{str(exc)}'})
return Response({'code':109,'message':'服务器异常'})
【七】验证部分异常
class BookView(GenericViewSet,ListModelMixin, RetrieveModelMixin, UpdateModelMixin,DestroyModelMixin,CreateModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerialzier
def list(self, request, *args, **kwargs):
# 定制返回格式
# return super().list(request,*args,**kwargs)
res = super().list(request,*args,**kwargs)
# l=[1,2,3]
# print(l[9]) # "程序异常,异常信息是:list index out of range"
# 100/0 # "程序异常,异常信息是:division by zero"
# raise Exception('出错咯') # "程序异常,异常信息是:出错咯"
# raise ValueError('sfzgdx') # "程序异常,异常信息是:sfzgdx"
return super().list(request,*args,**kwargs)
分类:
DRF
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY