一、路由
1)自动生成路由
'''
首先想自动生成路由就必须继承ViewSetmixin及其子类的视图类
'''
# 模拟一个自动生成路由练习
models.py # 创建一个书模型
class Book(models.Model):
name = models.CharField(max_length=32, )
price = models.CharField(max_length=32, )
publish = models.CharField(max_length=32)
views.py # 快速写好五个接口
from rest_framework.viewsets import ViewSetMixin, ViewSet, GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
class BookView(ModelViewSet): # 必须继承ViewSetmixin及其子类的视图类
queryset = Book.objects.all()
serializer_class = BookSerializer
serializer.py # 序列化
from .models import Book
from rest_framework import serializers
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__' # 所有字段
urls.py # API接口
urlpatterns = [
path('admin/', admin.site.urls),
path('books/', views.BookView.as_view({'get': 'list', 'post': 'create'})),
path('books/<int:pk>/', views.BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
]
'''
正常来说这里我们的五个几口写完了 但是并没有实现自动生成API
想要实现自动生成API需要使用到模块 rest_framework.routers里的SimpleRouter, DefaultRouter
'''
from rest_framework.routers import SimpleRouter, DefaultRouter # 导入模块
router = SimpleRouter() # 实例化路由
router.register('books', views.BookView, 'books') !!!! 注意可以注册多个
# 注册 第一参数 路径 第二个参数 视图类 第三个参数 别名
把自动生成的路由添加到Urlpatterns中 总共有两种方法:
方式1: # 直接添加 +=
urlpatterns+=router.urls
方式2: # 添加到urlpatterns里面使用到include
[path('', include(router.urls))]
'''
这个时候就能看到自动生成的路由 这个自动生成的路由就写好了
admin/
^books/$ [name='books-list']
^books/(?P<pk>[^/.]+)/$ [name='books-detail']
如果使用DefaultRouter 就会发生一个变化 多几个路由 主要的是多了一个跟路径 可以显示注册过的路由以及美化的页面
admin/
^books/$ [name='books-list']
^books\.(?P<format>[a-z0-9]+)/?$ [name='books-list']
^books/(?P<pk>[^/.]+)/$ [name='books-detail']
^books/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='books-detail']
^$ [name='api-root']
^\.(?P<format>[a-z0-9]+)/?$ [name='api-root']
'''
2)Action装饰器的使用
'''
Action主要用于在视图函数中含有一些其他名称的方法 这个时候只有5个参数(create, update, delete...)
我们不知道怎么去执行它 这个时候就必须要使用action装饰器做映射了
'''
views.py
class UserView(ViewSet):
def login(self, request):
return Response('ok')
urls.py
from rest_framework.routers import SimpleRouter, DefaultRouter
router = SimpleRouter()
router.register('users', views.UserView, 'users')
urlpatterns += router.urls
-------------------------------------------------------------------------
# 视图类中有其他名称的方法了需要使用Action装饰器了
views.py
class UserView(ViewSet):
@action(methods=['GET', 'POST'], detail=False, url_path='login', url_name='login')
def login(self, request):
return Response('ok')
# 自动生产的路由
^users/login/$ [name='users-login']
'''
method: 能够支持的请求方式 以列表的方式存入
detail: 默认是False 控制生成的路由是什么样子 ^users/(?P<pk>[^/.]+)/login/$ [name='users-login']如果是True则会加上PK
ulr_path: 控制生成的/user/后面的路径是什么 如果不写默认以方法名
url_name: 别名 用于反向解析
'''
二、认证
1)编写登录接口
# 认证是基于登录的接口上面操作的 所以前戏编写一个简单的登录接口
models.py
class User(models.Model): # 简易的用户信息账号密码
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
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) # 如果用户没有登录则没有值 如果登录则有值
views.py
class UserView(ViewSet):
@action(methods=['POST'], detail=False, url_path='login', url_name='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()) # uuid4 随机获得永不重复的字符串 机制跟Cookie中的验证码一样
UserToken.objects.update_or_create(defaults={'token': token}, user=user)
# 通过user去UserToken表中查数据,如果能查到,使用defaults的数据更新,如果查不到,直接通过user和defaults的数据新增
return Response({'code': 100, 'msg': '登录成功', 'token': token})
else:
return Response({'code': 101, 'msg': '用户名或密码错误'})
urls.py
from rest_framework.routers import SimpleRouter, DefaultRouter
router = SimpleRouter()
router.register('users', views.UserView, 'users')
urlpatterns += router.urls
'''这个时候一个简单的登录接口就写好了 每次登录都会更新Token 相当于登录了之前的设备就无效了 '''
2)认证类
'''
现在我们又了登录接口 但是我们想其他的接口必须登录之后才能访问呢?
思路:
通过认证类完成,使用步骤
1 写一个认证类,继承BaseAuthentication
2 重写authenticate方法,在内部做认证
3 如果认证通过,返回2个值
4 认证不通过抛AuthenticationFailed异常
5 只要返回了两个值,在后续的request.user 就是当前登录用户
'''
from .models import UserToken
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
class LoginAuth(BaseAuthentication):
def authenticate(self, request): # 重写authenticate方法 做内部认证
token = request.GET.get('token') # 获取Token
user_token = UserToken.objects.filter(token=token).first() # 对比token
if user_token:
return user_token.user, token # 如果通过认证返回两个值 不通过抛异常
else: # 只要返回了两个值,在后续的request.user 就是当前登录用户
raise AuthenticationFailed('您还没有登录哦')
'''
如果想让某个视图类登录后才能访问
方式1: 局部认证
from .auth import LoginAuth
class BookView(ModelViewSet):
authentication_classes = [LoginAuth,]
queryset = Book.objects.all()
serializer_class = BookSerializer
方式2: 全局认证
REST_FRAMEWORK={
'DEFAULT_AUTHENTICATION_CLASSES':['app01.auth.LoginAuth',]
}
全局认证但是又不想某一个不认证呢?
在视图类里面写入authentication_classes = []清空即可!!!
'''
三、权限
'''
权限就是相当于我们的管理员 没有权限就是普通用户
类似于python中的admin管理员我们只需要给用户加上字段admin 1,超级管理员 2,普通用户
我们现在目的 出版社所有的接口必须登录而且只有管理员才能够访问(延着上放模型继续写)
'''
models.py
class User(models.Model): # 简易的用户信息账号密码
username = models.CharField(max_length=32)
password = models.CharField(max_length=32) # 管理员字段
user_type = models.IntegerField(default=3, choices=((1, '超级管理员用户'), (2, '普通用户'), (3, '3b用户')))
'''
权限类其实跟认证类一样只需要继承类修改方法
思路:
-第一步:写一个类,继承BasePermission
-第二步:重写has_permission方法
-第三步:在方法中校验用户是否有权限(request.user就是当前登录用户)
-第四步:如果有权限,返回True,没有权限,返回False
-第五步:self.message 是给前端的提示信息
'''
permission.py # 创建的类文件
from rest_framework.permissions import BasePermission
class UserTypePermission(BasePermission): # 随意一个类继承BasePermission
def has_permission(self, request, view): # 重写has_permission方法
if request.user.user_type == 1: # 获取用户的管理员信息 如果有权限返回True 没权限False
return True
else: # 报错信息渲染
self.message = '您是%s, 您没有权限访问哦!' % request.user.get_user_type_display()
return False
'''
使用方法:
1.全局使用
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ['app01.permission.UserTypePermission']
}
2.局部使用
class PublishView(ListCreateAPIView):
permission_classes = [UserTypePermission, ]
3.局部禁用
class PublishView(ListCreateAPIView):
permission_classes = [ ]
'''
四、频率
'''
频率就涉及到爬虫方面的 为了防止网站被攻击一次性发了10多万次访问 这个时候我们就可以做一个频率限制 一分钟让访问几次
思路:
-第一步:写一个类:继承SimpleRateThrottle
-第二步:重写get_cache_key,返回唯一的字符串,会以这个字符串做频率限制
-第三步:写一个类属性scop='随意写',必须要跟配置文件对象
-第四步:配置文件中写
'DEFAULT_THROTTLE_RATES': {
'随意写': '3/m' # 3/h 3/s 3/d
}
'''
thorttling.py # 自定频率类文件
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
class MyThrottling(SimpleRateThrottle): # 继承SimpleRateThrottle
scope = 'luffy' # 类属性 属性名称可以随意取 但是要跟配置文件对应
def get_cache_key(self, request, view):
return request.META.get('REMOTE_ADDR')
'''
返回什么 频率就可以做什么限制
通过用户IP地址进行限制 REMOTE_ADDR
'''
settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'luffy': '5/m' # m分 h时 s秒 d天次数
},
}
'''
使用方法:
1.全局使用
REST_FRAMEWORK = {
''DEFAULT_THROTTLE_CLASSES': ['app01.throttling.MyThrottling'],
}
2.局部使用
class BookView(ModelViewSet):
throttle_classes = [MyThrottling, ]
3.局部禁用
class BookView(ModelViewSet):
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类中找:220行
def user(self):
if not hasattr(self, '_user'): # Request类的对象中反射_user
with wrap_attributeerrors():
self._authenticate() # 第一次会走这个代码
return self._user
-Request的self._authenticate()---》373行
def _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)异常
六、权限源码分析
-先读最简单的权限执行流程---》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 True或False,messgage可以做什么用
七、简单分析频率源码
# 源码分析:
-之前咱们读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(到了频率的限制)
技术小白记录学习过程,有错误或不解的地方请指出,如果这篇文章对你有所帮助请
点点赞收藏+关注
谢谢支持 !!!