认证,权限,频率,过滤-排序,分页,全局异常

Ⅰ 认证

# 三大认证依次执行
	-认证类
    -权限类
    -频率类
    
# 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)

posted on   silence^  阅读(4)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示