【Django-rest-framework框架】第07回 权限类、频率类

1. 权限类使用

1.1 models.py

class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    user_type = models.IntegerField(default=1, choices=((1, '高级管理员'), (2, '普通管理员'), (3, '普通用户')))

    # choices本质就是关联,后期不会修改的情况下使用choice,后期会增加或减少用户类型的情况下,要使用另一个表
    # user_type = models.IntegerField(default=1)   外键关联

    def __str__(self):
        return self.username

# class UserType(models.Model):
#     name = models.CharField(max_length=32)

class UserToken(models.Model):
    name = models.OneToOneField(to='User', on_delete=models.CASCADE)
    token = models.CharField(max_length=32, null=True)

1.2 创建permission.dy

from rest_framework.permissions import BasePermission


class UserTyprPermission(BasePermission):
    def has_permission(self, request, view):
        # 只有超级管理员有权限
        if request.user.user_type == 1:
            return True  # 有权限
        else:
            # self.message = '普通管理员和普通用户都没有权限' # 返回给前端的提示是什么样
            # 使用了choice后,user.user_type 拿到的是数字类型,想变成字符串 user.get_user_type_display()
            self.message = '您是:%s 用户,您没有权限' % request.user.get_user_type_display()
            return False  # 没权限

普通用户

高级管理员

1.3 使用步骤

第一步:写一个类,继承BasePermission
第二步:重写has_permission方法
第三步:在方法中校验用户是否有权限(request.user就是当前登录用户)
第四步:如果有权限,返回True,没有权限,返回False
第五步:self.message 是给前端的提示信息
第六步:
局部使用
permission_classes = [UserTyprPermission, ]
全局使用
REST_FRAMEWORK={
    "DEFAULT_PERMISSION_CLASSES":["app01.permission.UserTypePermission",]
}
局部禁用
permission_classes = []

2. 频率类使用

2.1 创建throttling.py文件

文件名throttling名字是随便取的,跟规范一点命名为throttling
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle


class OutThrottling(SimpleRateThrottle):  # 我们继承SimpleRateThrottle去写,而不是继承BaseThrottle去写
    # 类属性,这个类属性可以随意命名,但要跟配置文件对应
    scope = 'ling'
    def get_cache_key(self, request, view):
        # 返回什么,频率就以什么做限制
        # 可以通过用户id限制
        # 可以通过ip地址限制
        return request.META.get('REMOTE_ADDR')

2.2 使用步骤

第一步:写一个类:继承SimpleRateThrottle
第二步:重写get_cache_key,返回唯一的字符串,会以这个字符串做频率限制
第三步:写一个类属性scop='随意写',必须要配置文件对象对应
第四步:配置文件中写
'DEFAULT_THROTTLE_RATES': {
        '随意写': '3/m'  # 3/h  3/s  3/d
    	}
第五步:
局部配置
throttle_classes = [OutThrottling, ]
全局配置
REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_RATES": {'ling': '3/m'},
    'DEFAULT_THROTTLE_CLASSES': ['app01.throttling.OutThrottling'],
}

局部禁用
throttle_classes = []

3. 认证源码分析

# 写个认证类,重写某个方法,配置在视图类上,就有认证了---》认证类加了,在视图类的方法中,request.user就是当前登录用户---》猜认证类的执行,是在在视图类的方法之前执行的
# 源码分析:
    读APIView的执行流程---》包装了新的request,执行了3大认证,执行视图类的方法,处理了全局异常
    入口:APIView的dispatch
    APIView的dispatch的496行上下:self.initial(request, *args, **kwargs)
    APIView的initial
    413行上下:有三句话,分别是:认证,权限,频率
    	self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)
    读认证类的源码---》APIView的perform_authentication(request)315行上下
        def perform_authentication(self, request):
            request.user  # 新的request
    request是新的request---》Request类中找user属性(方法),是个方法包装成了数据属性
    来到Request类中找:220def user(self):
            if not hasattr(self, '_user'): # Request类的对象中反射_user
                with wrap_attributeerrors():
                    self._authenticate()  # 第一次会走这个代码
            return self._user
    -Request的self._authenticate()---373def _authenticate(self):
            for authenticator in self.authenticators: # 配置在视图类中所有的认证类的对象 
                try:
                    #(user_token.user, token)
                    user_auth_tuple = authenticator.authenticate(self) # 调用认证类对象的authenticate
               except exceptions.APIException:
                    self._not_authenticated()
                    raise

                if user_auth_tuple is not None:
                    self._authenticator = authenticator # 忽略
                    self.user, self.auth = user_auth_tuple # 解压赋值
                    return
			  # 认证类可以配置多个,但是如果有一个返回了两个值,后续的就不执行了
            self._not_authenticated()

总结:认证类源码
     认证类,要重写authenticate方法,认证通过返回两个值或None,认证不通过抛AuthenticationFailed(继承了APIException)异常

4. 权限源码分析

先读最简单的权限执行流程---》APIView的check_permissions(request)325行上下
    def check_permissions(self, request):
        for permission in self.get_permissions():
            # permission是咱们配置在视图类中权限类的对象,对象调用它的绑定方法has_permission
            # 对象调用自己的绑定方法会把自己传入(权限类的对象,request,视图类的对象)
            if not permission.has_permission(request, self):
                self.permission_denied(
                    request,
                    message=getattr(permission, 'message', None),
                    code=getattr(permission, 'code', None)
                )
                
   APIVIew的self.get_permissions()273行上下
	return [permission() for permission in self.permission_classes]
   self.permission_classes 就是咱们在视图类中配的权限类的列表
   所以这个get_permissions返回的是 咱们在视图类中配的权限类的对象列表[UserTypePermession(),]


总结:权限类源码
     为什么要写一个类,重写has_permission方法,有三个参数,为什么一定要return TrueFalse,messgage可以做什么用

5. 频率类源码

之前咱们读APIView的执行流程---》包装了新的request,执行了3大认证,执行视图类的方法,处理了全局异常
    入口:APIView的dispatch
    APIView的dispatch的496行上下:self.initial(request, *args, **kwargs)
    APIView的initial
    413行上下:有三句话,分别是:认证,权限,频率
    	self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)
    APIView的check_throttles:351上下
        def check_throttles(self, request):
            throttle_durations = []
            for throttle in self.get_throttles():
                if not throttle.allow_request(request, self):
                    throttle_durations.append(throttle.wait())
                    
                    
总结:要写频率类,必须重写allow_request方法,返回True(没有到频率的限制)或False(到了频率的限制)

6. 鸭子类型

简单理解:走路像鸭子,说话像鸭子,它就是鸭子

专业术语:指的是面向对象中,子类不需要显示的继承某个类,只要有某个的方法和属性,那我就属于这个类

假设有个鸭子类Duck类,有两个方法,run,speak方法
假设又有一个普通鸭子类,PDuck,如果它也是鸭子,它需要继承Duck类,只要继承了鸭子类,什么都不需要写,普通鸭子类的对象就是鸭子这种类型;如果不继承,普通鸭子类的对象就不是鸭子这种类型
假设又有一个唐老鸭子类,TDuck,如果它也是鸭子,它需要继承Duck类,只要继承了鸭子类,什么都不需要写,唐老鸭子类的对象就是鸭子这种类型;如果不继承,唐老鸭子类的对象就不是鸭子这种类型

python不推荐这个,推荐鸭子类型,指的是:不需要显示的继承某个类,只要我的类中有run和speak方法,我就是鸭子这个类

问题:如果使用python鸭子类型写法,如果方法写错了,它就不是这个类型了,会有问题
python为了解决这个问题:
   方式一:abc模块,装饰后,必须重写方法,不重写就报错
   方式二:drf源码中使用的:父类中写这个方法,但没有具体实现,直接抛异常

7. django的配置文件不要乱导入,乱导入可能会出错

django的运行是在加载完配置文件后才能运行
因为模块的导入会执行那个模块,而这个模块中又有别的导入,别的导入必须djagno运行起来才能使用

作业

1 编写图书和出版社的5个接口,所有接口都要有一分钟访问5次的频率限制
2 图书的接口需要登录才能方法,出版社的接口需要登录,并且是超级用户才能访问
models.py

from django.db import models


# Create your models here.


class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.CharField(max_length=32)
    publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)


class Publish(models.Model):
    name = models.CharField(max_length=32)
    address = models.CharField(max_length=32)


class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    user_type = models.IntegerField(default=1, choices=((1, '高级管理员'), (2, '普通管理员'), (3, '普通用户')))

    def __str__(self):
        return self.username


class UserToken(models.Model):
    user = models.OneToOneField(to='User', on_delete=models.CASCADE)
    token = models.CharField(max_length=32, null=True)

serializer.py

from .models import Book, Publish
from rest_framework import serializers


class BookSerializers(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'


class PublishSerializer(serializers.ModelSerializer):
    class Meta:
        model = Publish
        fields = '__all__'

views.py

from django.shortcuts import render

# Create your views here.

from .models import Book, Publish, User, UserToken
from .serializer import BookSerializers, PublishSerializer
from rest_framework.viewsets import ModelViewSet, ViewSet
from rest_framework.response import Response
from rest_framework.decorators import action
import uuid
from .auth import LoginAuth


class BookView(ModelViewSet):
    authentication_classes = [LoginAuth, ]
    queryset = Book.objects.all()
    serializer_class = BookSerializers


from .permissions import UserTypePermission


class PublishView(ModelViewSet):
    authentication_classes = [LoginAuth, ]
    permission_classes = [UserTypePermission, ]
    queryset = Publish.objects.all()
    serializer_class = PublishSerializer


class UserView(ViewSet):
    @action(methods=['POST', ], detail=False, url_path='login')
    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 = str(uuid.uuid4())
            UserToken.objects.update_or_create(defaults={'token': token}, user=user)
            return Response({'code': 100, 'msg': '登陆成功', 'token': token})
        else:
            return Response({'code': 101, 'msg': '用户名或密码错误'})

auth.py

from .models import UserToken
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed


class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        token = request.GET.get('token')
        user_token = UserToken.objects.filter(token=token).first()
        if user_token:
            return user_token.user, token
        else:
            raise AuthenticationFailed('您没有登录')

permission.py

from rest_framework.permissions import BasePermission


class UserTypePermission(BasePermission):
    def has_permission(self, request, view):
        if request.user.user_type == 1:
            return True
        else:
            self.message = '您是:%s ,您没有权限' % request.user.get_user_type_display()
            return False

throttling.py

from rest_framework.throttling import SimpleRateThrottle


class OutThrottling(SimpleRateThrottle):
    scope = 'ling'

    def get_cache_key(self, request, view):
        return request.META.get('REMOTE_ADDR')

urls.py

from django.contrib import admin
from django.urls import path
from app01 import views
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register('books', views.BookView, 'books')
router.register('publish', views.PublishView, 'publish')
router.register('user', views.UserView, 'user')

urlpatterns = [
    path('admin/', admin.site.urls),
]

urlpatterns += router.urls

settings.py

REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_RATES": {'ling': '5/m'},
    'DEFAULT_THROTTLE_CLASSES': ['app01.throttling.OutThrottling'],
}
posted @   |相得益张|  阅读(58)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示