DRF-(6)

内容概览

  • 权限类使用
  • 频率类使用
  • 认证源码分析
  • 权限源码分析
  • 频率源码简单分析
  • 鸭子类型

权限类使用

"""
第一步:写一个类继承BasePermission
第二步:重写BasePermission中的has_permission方法
第三步:在方法中校验用户是否有权限(用户认证后request.user就是当前用户)
第四步:如果有权限返回True,否则返回False
第五步:使用self.message自定义给前端返回的信息
第六步:全局使用/局部使用/局部禁用
"""
from rest_framework.permissions import BasePermission
"""写一个类,继承BasePermission"""
class BookPermission(BasePermission):
	message = '返回给前端的提示信息'
	"""重写has_permission方法"""
    def has_permission(self, request, view):
    	"""校验用户权限,通过返回True,否则返回False"""
        user_type = request.user.user_type
        # self.message = f'当前为{request.user.get_user_type_display()},暂无权限'  # 使用choice字段属性后,直接获取字段拿到的是数字类型,想要拿到字符串需要使用get_字段名_display()
        return True if user_type == 1 else False

在setting中配置全局生效

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': ['app01.permissions.BookPermission']
}

在视图类中配置局部生效或局部禁用

class UserView(ViewSet):
	permission_classes = [BookPermission,]  # 局部生效
    permission_classes = []  # 局部禁用

频率类使用

"""
第一步:写一个类继承SimpleRateThrottle
第二步:重写get_cache_key方法,返回唯一的字符串(用户id/ip地址),会以这个字符串作为频率限制的依据
第三步:写一个类属性scope = 'xxx',值可以任意字符,需要与配置文件对应
第四步:编写配置文件
"""
from rest_framework.throttling import SimpleRateThrottle

class Throttle_Class(SimpleRateThrottle):
    scope = 'scope'

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

配置文件:

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_RATES': {
            'scope':'3/m'  # 与类属性对应,参数为多长时间能够访问多少次;s/秒,m/分,h/小时,d/天
        },
    'DEFAULT_THROTTLE_CLASSES': ['app01.throttle.Throttle_Class'],
}  # 全局配置

视图函数:

class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
	throttle_classes = [Throttle_Class,]  # 局部配置
    throttle_classes = []  # 局部禁用

认证源码分析

# 写个认证类,重写某个方法,配置在视图类上,就有认证了---》认证类加了,在视图类的方法中,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: # 配置在视图类中所有的认证类的对象,在实例化Request对象时通过推导式获取
            """
			def get_authenticators(self):
        		return [auth() for auth in self.authentication_classes]
			"""
                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)异常

权限源码分析

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):  # 如果返回值为False执行if内代码,返回True则直接跳过
                self.permission_denied(
                    request,
                    message=getattr(permission, 'message', None),
                    code=getattr(permission, 'code', None)
                )  # 内部实际上也是使用raise抛错
                
   -APIVIew的self.get_permissions()273行上下
	return [permission() for permission in self.permission_classes]
   -self.permission_classes 就是咱们在视图类中配的权限类的列表
   -所以这个get_permissions返回的是 咱们在视图类中配的权限类的对象列表[UserTypePermession(),]

频率源码简单分析

# 源码分析:
	-之前咱们读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(到了频率的限制)

鸭子类型

假设有A和B两个类,B不需要继承A这个类,只要B中有A的属性和方法,那么它就是A这个类
但如果方法写错了,那么就不是A这个类了,python解决这个问题有两种方法

  1. abc模块,装饰后必须重写被装饰的方法,不然就报错
  2. drf源码中使用的,父类中的这个方法直接抛异常

练习

# 1 编写图书和出版社的5个接口,所有接口都要有一分钟访问5次的频率限制
# 2 图书的接口需要登录才能方法,出版社的接口需要登录,并且是超级用户才能访问
# 3 继承BaseThrottle编写频率类
	# 自定义的逻辑  {ip:[时间1,时间2]}
    #(1)取出访问者ip
    #(2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
    #(3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
    #(4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
    #(5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败

models.py

from django.db import models

class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    user_type = models.IntegerField(choices=((1, '超极管理员'), (2, '会员用户'), (3, '普通用户')), default=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=64, null=True)


class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    # publish_date = models.DateField(null=True)

    publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)

    def publish_dict(self):
        return {'name':self.publish.name, 'city': self.publish.city, 'email': self.publish.email}

    def __str__(self):
        return self.name


class Publish(models.Model):
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)
    email = models.EmailField()

    def __str__(self):
        return self.name

urls.py

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

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

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

serializers

from rest_framework.serializers import ModelSerializer
from app01 import models


class BookModelSerializer(ModelSerializer):
    class Meta:
        model = models.Book
        fields = ['id', 'name', 'price', 'publish', 'publish_dict']
        extra_kwargs = {
            'publish': {'write_only': True},
            'publish_dict': {'read_only': True},
        }


class PublishModelSerializer(ModelSerializer):
    class Meta:
        model = models.Publish
        fields = '__all__'

views.py

import uuid

from django.shortcuts import render
from django.http import JsonResponse
from rest_framework.viewsets import ModelViewSet, ViewSet
from rest_framework.decorators import action
from app01 import models
from app01 import serializers


# Create your views here.


class UserView(ViewSet):
    authentication_classes = []
    permission_classes = []

    @action(['POST', ], detail=False, url_path='login', url_name='login')
    def login(self, request):
        username = request.POST.get('username')
        password = request.POST.get('password')
        user_obj = models.User.objects.filter(username=username, password=password).first()
        if user_obj:
            token = str(uuid.uuid4())
            models.UserToken.objects.update_or_create({'token': token}, user=user_obj)
            return JsonResponse({'code': 100, 'msg': '登录成功'})
        else:
            return JsonResponse({'code': 101, 'msg': '用户名或密码错误'})


class BookView(ModelViewSet):
    queryset = models.Book.objects.all()
    serializer_class = serializers.BookModelSerializer
    permission_classes = []


class PublishView(ModelViewSet):
    queryset = models.Publish.objects.all()
    serializer_class = serializers.PublishModelSerializer

三大认证

"""authentication"""
from rest_framework.authentication import BaseAuthentication
from app01 import models
from rest_framework.exceptions import AuthenticationFailed


class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        token = request.META.get('HTTP_TOKEN')
        user_token_obj = models.UserToken.objects.filter(token=token).first()
        if user_token_obj:
            return user_token_obj.user, token
        else:
            raise AuthenticationFailed('当前没有登录')
"""permissions"""
from rest_framework.permissions import BasePermission


class App01_Permission(BasePermission):
    def has_permission(self, request, view):
        pm = request.user.user_type
        if pm == 1:
            return True
        else:
            self.message = f'当前为{request.user.get_user_type_display()},权限不足'
            return False
"""throttles"""
import datetime

from rest_framework.throttling import SimpleRateThrottle, BaseThrottle


class Throttle_app01(BaseThrottle):
    scope = 'scope'
    user_throttle = {}
    def allow_request(self, request, view):
        print(self.user_throttle)
        user_ip = request.META.get('REMOTE_ADDR')
        if user_ip not in self.user_throttle:
            self.user_throttle[user_ip] = [datetime.datetime.now()]
            return True
        else:
            self.user_throttle[user_ip].insert(0, datetime.datetime.now())
            time_delta = datetime.timedelta(minutes=1)
            time_list = self.user_throttle[user_ip]
            for i in time_list:
                if i < datetime.datetime.now() - time_delta:
                    self.user_throttle.get(user_ip).pop()
            if len(self.user_throttle.get(user_ip)) > 5:
                return False
            return True
"""setttings"""
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': ['app01.authenticates.LoginAuth'],
    'DEFAULT_PERMISSION_CLASSES': ['app01.permissions.App01_Permission'],
    # 'DEFAULT_THROTTLE_RATES': {
    #     'scope': '5/m'
    # },
    'DEFAULT_THROTTLE_CLASSES': ['app01.throttles.Throttle_app01'],
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值